序文
私たちのアプリケーションは、DELETE
クエリを並行して実行するいくつかのスレッドを実行します。クエリは分離されたデータに影響します。つまりDELETE
、別々のスレッドからの同じ行で同時発生する可能性はありません。ただし、ドキュメントごとに、MySQLはDELETE
ステートメントにいわゆる「次のキー」ロックを使用します。これにより、一致するキーと一部のギャップの両方がロックされます。このことはデッドロックを引き起こし、私たちが見つけた唯一の解決策はREAD COMMITTED
分離レベルを使用することです。
問題
巨大なテーブルDELETE
がJOIN
複数ある複雑なステートメントを実行すると問題が発生します。特定のケースでは、警告が2行しかないテーブルがありますが、クエリは2つの個別のINNER JOIN
edテーブルから特定のエンティティに属するすべての警告を削除する必要があります。クエリは次のとおりです。
DELETE pw
FROM proc_warnings pw
INNER JOIN day_position dp
ON dp.transaction_id = pw.transaction_id
INNER JOIN ivehicle_days vd
ON vd.id = dp.ivehicle_day_id
WHERE vd.ivehicle_id=? AND dp.dirty_data=1
day_positionテーブルが十分に大きい場合(私のテストケースでは1448行あります)、READ COMMITTED
分離モードでもトランザクションはテーブル全体を ブロックしproc_warnings
ます。
この問題は常にこのサンプルデータ(http://yadi.sk/d/QDuwBtpW1BxB9)で再現されます(MySQL 5.1(5.1.59でチェック)およびMySQL 5.5(MySQL 5.5.24でチェック))。
編集:リンクされたサンプルデータには、クエリテーブルのスキーマとインデックスも含まれています。
CREATE TABLE `proc_warnings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned NOT NULL,
`warning` varchar(2048) NOT NULL,
PRIMARY KEY (`id`),
KEY `proc_warnings__transaction` (`transaction_id`)
);
CREATE TABLE `day_position` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`transaction_id` int(10) unsigned DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_day_id` int(10) unsigned DEFAULT NULL,
`dirty_data` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `day_position__trans` (`transaction_id`),
KEY `day_position__is` (`ivehicle_day_id`,`sort_index`),
KEY `day_position__id` (`ivehicle_day_id`,`dirty_data`)
) ;
CREATE TABLE `ivehicle_days` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`d` date DEFAULT NULL,
`sort_index` int(11) DEFAULT NULL,
`ivehicle_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `ivehicle_days__is` (`ivehicle_id`,`sort_index`),
KEY `ivehicle_days__d` (`d`)
);
トランザクションごとのクエリは次のとおりです。
トランザクション1
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;
トランザクション2
set transaction isolation level read committed; set autocommit=0; begin; DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1;
それらの1つは常に「ロック待機タイムアウトを超えました...」エラーで失敗します。にinformation_schema.innodb_trx
は次の行が含まれます。
| trx_id | trx_state | trx_started | trx_requested_lock_id | trx_wait_started | trx_wait | trx_mysql_thread_id | trx_query |
| '1A2973A4' | 'LOCK WAIT' | '2012-12-12 20:03:25' | '1A2973A4:0:3172298:2' | '2012-12-12 20:03:25' | '2' | '3089' | 'DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1' |
| '1A296F67' | 'RUNNING' | '2012-12-12 19:58:02' | NULL | NULL | '7' | '3087' | NULL |
information_schema.innodb_locks
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67' | 'X' | 'RECORD' | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
両方のクエリでX
、主キー= 53の行に対する排他ロックが必要であることがわかりproc_warnings
ます。ただし、どちらのクエリもテーブルから行を削除する必要はありません。インデックスがロックされている理由がわかりません。さらに、proc_warnings
テーブルが空の場合、またはday_position
テーブルに含まれる行数が少ない場合(つまり、100行)、インデックスはロックされません。
さらなる調査はEXPLAIN
、同様のSELECT
クエリを実行することでした。これは、クエリオプティマイザーがproc_warnings
テーブルのクエリにインデックスを使用しないことを示しています。これが、主キーインデックス全体をブロックする理由を想像できる唯一の理由です。
簡略化されたケース
また、レコードが2つあるテーブルが2つしかなく、子テーブルの親テーブルのref列にインデックスがない場合にも、問題はより簡単なケースで再現できます。
parent
テーブルを作成
CREATE TABLE `parent` (
`id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
child
テーブルを作成
CREATE TABLE `child` (
`id` int(10) unsigned NOT NULL,
`parent_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
テーブルを埋める
INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);
2つの並列トランザクションでテストします。
トランザクション1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 1;
トランザクション2
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET AUTOCOMMIT=0; BEGIN; DELETE c FROM child c INNER JOIN parent p ON p.id = c.parent_id WHERE p.id = 2;
どちらの場合にも共通するのは、MySQLがインデックスを使用しないことです。それがテーブル全体をロックする理由だと思います。
私たちのソリューション
現時点で確認できる唯一の解決策は、デフォルトのロック待機タイムアウトを50秒から500秒に増やして、スレッドがクリーンアップを完了するようにすることです。その後、指を交差させてください。
助けてくれてありがとう。
day_position
テーブルの実行が遅くなりすぎてタイムアウト制限を500秒に上げなければならない場合、テーブルには通常いくつの行が含まれていますか?2)サンプルデータしかない場合、実行にどのくらいの時間がかかりますか?