web-dev-qa-db-ja.com

MySQLインデックスの作成がテーブルで失敗する

更新:tl; dr:問題は、MySQLがインデックスを作成するときにTMPDIRを使用することでした。そして、私のTMPDIRは、ディスク領域が不足していました。

元のQ:

InnoDBテーブルにインデックスを追加しようとしていて、table is full error。十分なディスク容量があり、MySQL構成にはfile-per-table = 1があります。テーブルデータは85GBで、インデックスは約20GB-30GBであり、それよりもはるかに多くのディスク容量があると思います。私はext3も使用しているので、OSの観点から見たファイルサイズの制限に問題はないと思います。

ログに記録されたエラーは次のようになります。

140616 13:04:33  InnoDB: Error: Write to file (merge) failed at offset 3 1940914176.
InnoDB: 1048576 bytes should have been written, only 970752 were written.
InnoDB: Operating system error number 0.
InnoDB: Check that your OS and file system support files of this size.
InnoDB: Check also that the disk is not full or a disk quota exceeded.
InnoDB: Error number 0 means 'Success'.
InnoDB: Some operating system error numbers are described at
InnoDB: http://dev.mysql.com/doc/refman/5.5/en/operating-system-error-codes.html
140616 13:04:33 [ERROR] /usr/libexec/mysqld: The table 'my_table' is full

これは何が原因で、どうすれば解決できますか?

作成テーブル:

`CREATE TABLE `my_table` (
 `uid_from` bigint(11) NOT NULL,
 `uid_to` bigint(11) NOT NULL,
 `counter` int(11) NOT NULL,
 `updated` date NOT NULL,
 PRIMARY KEY (`uid_to`,`uid_from`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8`

データは87.4GBであり、約1.5Bの行があると推定しています。

SHOW GLOBAL VARIABLES LIKE 'tmpdir';
Variable_name   Value
tmpdir  /tmp

[root@web ~]# df -h /tmp
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda1       40G   24G   14G  64% /
10
Noam

エラーメッセージThe table 'my_table' is fullに騙されないでください。このまれなシナリオは、ディスクスペースとはまったく関係ありません。このテーブルフルの状態は、InnoDBの内部配管に関係しています。

まず、このInnoDBアーキテクチャの図を見てください。

InnoDB Architecture

システムテーブルスペース(ファイルibdata)には 128ロールバックセグメント とロールバックセグメントごとに1023ロールバックスロットしかないことに注意してください。これにより、トランザクションのロールバック容量のサイズが制限されます。つまり、1つのロールバックセグメントがトランザクションをサポートするために1023を超えるスロットを必要とする場合、トランザクションはそのtable is full条件にヒットします。

ニュージャージー州のレストランRed Lobster を考えてみてください。それは200人の容量があるかもしれません。レストランが満員の場合は、列に並ぶ人が外に出ることがあります。行列ができなくなった場合は、満員のため退店することがあります。明らかに、解決策はニュージャージーを大きくする(またはディスク容量を増やす)ことではありません。解決策は、Red Lobsterレストランを大きくすることです。そうすれば、収容人数を240人まで増やすことができます。それでも、240人を超える人がレッドロブスターに来ることを決めた場合、外に列ができることがあります。

例を挙げれば、2TBのシステムテーブルスペースを備えたクライアントがあり、 innodb_file_per_table が無効になっています。 (ibdata1の346G、ibdata2のリセット)。このクエリを実行しました

SELECT SUM(data_length+index+length) InnoDBDataIndexSpace
FROM information_schema.tables WHERE engine='InnoDB';

次に、ibdata1とibdata2のファイルサイズの合計からInnoDBDataIndexSpaceを差し引きました。私は私に衝撃を与えた2つのものを得ました

  • システムテーブルスペース内に106GB残っていました。
  • これと同じTable is Full状態になりました

これは、106GBがInnoDBの内部配管に使用されていたことを意味します。当時、クライアントはext3を使用していました。

私にとっての解決策は、ibdata3を追加することでした。私はこれを以前の投稿で話しました 「テーブルがいっぱいです」を「innodb_file_per_table」で解決する方法は?

私は他の投稿でもこれについて議論しました

innodb_file_per_table が有効になっている場合でも、この状態が発生する可能性があることに注意してください。どうやって? ロールバックと元に戻すログは、ibdata1の制御されていないスパイクの原因です

あなたの実際の質問

インデックスをテーブルに追加してTable is Fullを取得しているため、テーブルは巨大である必要があり、単一のロールバックセグメント内に収まりません。次のことを行う必要があります。

ステップ01

データをダンプファイルに取得する

mysqldump --no-create-info mydb mytable > table_data.sql

ステップ02

MySQLにログインしてこれを実行します

USE mydb
CREATE TABLE mytable_new LIKE mytable;
ALTER TABLE mytable_new ADD INDEX ... ;
ALTER TABLE mytable RENAME mytable_old;
ALTER TABLE mytable_new RENAME mytable;

ステップ03

データを含む追加のインデックスをテーブルにロードします

mysql -Dmydb < table_data.sql

それで全部です。

問題は、ALTER TABLEが巨大なテーブルのすべての行を1つのトランザクションとして挿入しようとすることです。 mysqldumpを使用すると、単一のトランザクションのすべての行ではなく、一度に数千行のデータがテーブル(現在は新しいインデックス)に挿入されます。

what if this doesn't work?について心配する必要はありません。元のテーブルは、何があってもmytable_oldという名前になります。バックアップとして使用できます。新しいテーブルが機能することがわかったら、バックアップを削除できます。

試してみる !!!

UPDATE 2014-06-16 11:13 EDT

巨大なデータのダンプが心配な場合は、gzipしてください。

同じ手順を実行できますが、次のようになります

ステップ01

データをダンプファイルに取得する

mysqldump --no-create-info mydb mytable | gzip > table_data.sql.gz

ステップ02

MySQLにログインしてこれを実行します

USE mydb
CREATE TABLE mytable_new LIKE mytable;
ALTER TABLE mytable_new ADD INDEX ... ;
ALTER TABLE mytable RENAME mytable_old;
ALTER TABLE mytable_new RENAME mytable;

ステップ03

データを含む追加のインデックスをテーブルにロードします

gzip -d < table_data.sql.gz | mysql -Dmydb

または

gunzip < table_data.sql.gz | mysql -Dmydb

UPDATE 2014-06-16 12:55 EDT

私はこの問題に関して別の側面について考えました。

DMLではなくDDLを実行しているため、これがInnoDBの内部配管ではない可能性があります。 DDLはInnoDBをロールバックできない なので、問題は外部の配管である必要があります。この外部配管はどこで詰まっていますか? OSの一時フォルダーが疑われます。どうして?

140616 13:04:33  InnoDB: Error: Write to file (merge) failed at offset 3 1940914176.
InnoDB: 1048576 bytes should have been written, only 970752 were written.
InnoDB: Operating system error number 0.
InnoDB: Check that your OS and file system support files of this size.
InnoDB: Check also that the disk is not full or a disk quota exceeded.
InnoDB: Error number 0 means 'Success'.
InnoDB: Some operating system error numbers are described at
InnoDB: http://dev.mysql.com/doc/refman/5.5/en/operating-system-error-codes.html
140616 13:04:33 [ERROR] /usr/libexec/mysqld: The table 'my_table' is full

disk quota exceededをご覧ください。このディスククォータはどこに課されていますか?

このクエリを実行する

SHOW GLOBAL VARIABLES LIKE 'tmpdir';

/var/lib/mysqltmpだとおっしゃっていました

これをOSで実行します

df -h /var/lib/mysqltmp

何かにより、/var/lib/mysqltmpのスペースが不足していることがわかります。結局のところ、14Gしか空き容量がありません。 DDL(インデックスを作成するため)の目には、ibdata1ファイルではなく、tmpdirの場所でロールバックが発生しました。 /var/lib/mysqltmpがどこにもマウントされていない場合、一時データはルートパーティションに書き込まれています。 /var/lib/mysqltmpがどこかにマウントされている場合、そのマウントは行データで埋められています。どちらの場合も、DDLを完了するための十分なスペースがありません。

ここには2つのオプションがあります

オプション1

大きなディスク(おそらく100 GB以上)を作成し、その大きなディスクに/var/lib/mysqltmpをマウントすることもできます。

オプション#2

私の3ステップの提案は、ディスクスペースが限られている場合でも機能するはずです。

解説

あなたが投稿したエラーメッセージが言うのは残念です

InnoDB: Operating system error number 0.
InnoDB: Error number 0 means 'Success'.

つまり、OSは問題ありません。また、この状況に関連するエラー番号はありません。

もう一つ知っておくべきことがあります。 MySQLドキュメントによると、InnoDBの高速インデックス作成はまだディスクに保存されます

インデックスの作成中、ファイルは一時ディレクトリ(Unixでは$ TMPDIR、Windowsでは%TEMP%、または--tmpdir構成変数の値)に書き込まれます。各一時ファイルは、新しいインデックスを構成する1つの列を保持するのに十分な大きさであり、最終的なインデックスにマージされるとすぐに削除されます。

MySQLの制限により、TEMPORARY TABLEでインデックスを作成するときに「高速インデックス作成」を使用するのではなく、テーブルがコピーされます。これは MySQL Bug#39833 として報告されています。

UPDATE 2014-06-16 13:58 EDT

[mysqld]
datadir                         = /mnt/cbsvolume1/var/lib/mysql
tmpdir                          = /mnt/cbsvolume1/var/lib/mysql

/mnt/cbsvolume1/var/lib/mysqlの空き容量が100G以上あることを確認してください

8
RolandoMySQLDBA