下記の記事のように複数行に対する UPDATE は BEGIN の無い 1 文でもデッドロックすることがあります。
これを見て思ったのですが INSERT であればバルクではない 1 行だけでもデッドロックすることがあります。
次のようなテーブルを用意します。
create table s ( sid int not null primary key ); create table t ( id int not null primary key, sid int not null, foreign key (sid) references s (sid) );
次のクエリでデッドロックします。
insert into t values (1,1);
実際に試してみます。次のように INSERT を並列にたくさん実行します。
seq 1 100 | xargs -P0 -i mysql test -u root -e 'insert into t values (1,1)'
外部キーの参照先が存在しないので外部キー制約のエラーがたくさん発生しますが、それに混じってデッドロックも発生します。
ERROR 1452 (23000) at line 1: Cannot add or update a child row: a foreign key constraint fails (`test`.`t`, CONSTRAINT `t_ibfk_1` FOREIGN KEY (`sid`) REFERENCES `s` (`sid`)) ERROR 1452 (23000) at line 1: Cannot add or update a child row: a foreign key constraint fails (`test`.`t`, CONSTRAINT `t_ibfk_1` FOREIGN KEY (`sid`) REFERENCES `s` (`sid`)) ERROR 1213 (40001) at line 1: Deadlock found when trying to get lock; try restarting transaction ERROR 1452 (23000) at line 1: Cannot add or update a child row: a foreign key constraint fails (`test`.`t`, CONSTRAINT `t_ibfk_1` FOREIGN KEY (`sid`) REFERENCES `s` (`sid`)) ERROR 1452 (23000) at line 1: Cannot add or update a child row: a foreign key constraint fails (`test`.`t`, CONSTRAINT `t_ibfk_1` FOREIGN KEY (`sid`) REFERENCES `s` (`sid`))
原因
実は外部キー制約は関係なく、INSERT が ROLLBACK されることに意味があります。次のように手操作でも簡単に発生させられます。
↑の例から外部キー制約も外した次のようなテーブルを用意します。
create table t ( id int not null primary key );
最初にトランザクションを開始して行を挿入します。
/* Tx.1 */ begin; insert into t values (1);
次に、2つのトランザクションで同じ行を挿入します。これは↑のトランザクションと競合するのでロック待ちになります。
/* Tx.2 */ insert into t values (1); /* Tx.3 */ insert into t values (1);
そして最初のトランザクションをロールバックします。
/* Tx.1 */ rollback;
すると、ロック待ちだった2番目と3番目のどちらかがデッドロックによりエラーになります。
/* Tx.3 */ insert into t values (1); /* ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction */
なぜこれがデッドロックするかは次のページに解説があるので割愛します(Tx.2 Tx.3 が共有ロックでロック待ちになったあと、Tx.1 がロールバックされたことで排他ロックを取ろうとしてデッドロック)。
最初に示した例は外部キー制約でエラーにすることで、明示的にトランザクションを開始させることなくロールバックさせている、ということになります。