kawabatas技術ブログ

試してみたことを書いていきます

外部キー制約の親子テーブルにおいて共有ロックからのdeadlock

deadlock のログを発見し、調べました。

結論はタイトルの通りです。

とりあえず、サンプルのSQLで試します。

CREATE TABLE players (
  id int primary key,
  name varchar(20)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

CREATE TABLE player_items (
  id int primary key,
  player_id int,
  item_id int,
  CONSTRAINT fk_player_id FOREIGN KEY (player_id) REFERENCES players (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO players (id, name) VALUES (1, "player_1");

f:id:kawabatas:20200621155640p:plain

ターミナル1

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO player_items (id, player_id, item_id) VALUES (1, 1, 1);
Query OK, 1 row affected (0.00 sec)

mysql> UPDATE players set name = "player_one" where id = 1;
Query OK, 0 rows affected (6.29 sec)
Rows matched: 1  Changed: 0  Warnings: 0

ターミナル2

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO player_items (id, player_id, item_id) VALUES (10, 1, 2);
Query OK, 1 row affected (0.00 sec)

mysql> UPDATE players set name = "player_ichi" where id = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
mysql>

deadlock の発生していたコードをみたとき、UPDATE 文でロック待ちをしても、deadlock が起こるとは思いませんでした。

SHOW ENGINE INNODB STATUS;を確認すると、

共有ロックかかっているレコードをUPDATEしようとしてデッドロックになっているようでしたが、コード上は明示的に共有ロックかけているようなところは見当たらず、なぜ共有ロックがかかっているのか不明でした。。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 14.6.6 InnoDB と FOREIGN KEY 制約

外部キーチェックの実行時に、InnoDB は、調査対象の子または親のレコード上に共有の行レベルロックを設定します。

どうやら MySQL の外部キー制約の仕様で、外部キー制約のテーブルへの INSERT で共有ロックがかかっているようです。

今回は一時的に SET FOREIGN_KEY_CHECKS=0 で外部キー制約を一時的に外すことで対応しました。