MySQLでデッドロックが発生すると、SHOW ENGINE INNODB STATUSLATEST DETECTED DEADLOCKに直近の詳細が記録される。
継続的に記録するにはinnodb_print_all_deadlocksを有効にし、エラーログに全件を残す。

参考: 【PostgreSQL】デッドロックのログの読み方

デッドロックとは

2つ以上のトランザクションが互いの保持するロックの解放を待ち合い、どちらも先に進めなくなった状態である。InnoDBはinnodb_deadlock_detect(デフォルトON)が有効な場合、ロック待ちが発生するたびに待ち合いのグラフを検査し、循環を見つけた時点で即座にトランザクションのひとつを中断する。PostgreSQLのdeadlock_timeoutのように検査を始めるまでの猶予時間はない。

以下、主キーidbalanceカラムを持つtestdb.accountsテーブルに対して、2つのセッションが逆の順序で行を更新し、デッドロックが発生した例を使う。

CREATE TABLE accounts (id INT PRIMARY KEY, balance INT);
INSERT INTO accounts VALUES (1, 1000), (2, 2000);
-- セッションA
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- ここでセッションBがid=2をロック
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- セッションB
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE id = 2;
-- ここでセッションAがid=1をロック
UPDATE accounts SET balance = balance + 200 WHERE id = 1;

クライアントに返るエラー

中断された側のクライアントには以下のエラーが返る。

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

SQLSTATEは40001、エラー番号は1213である。PostgreSQLと同様、クライアント側のエラーだけでは原因のクエリまでは分からない。詳細を調べるにはSHOW ENGINE INNODB STATUSを見る。

SHOW ENGINE INNODB STATUSでデッドロックの詳細を見る

SHOW ENGINE INNODB STATUSを実行すると、LATEST DETECTED DEADLOCKセクションに直近のデッドロックの詳細が出力される。

mysql> SHOW ENGINE INNODB STATUS\G
...
LATEST DETECTED DEADLOCK
------------------------
2026-08-22 23:46:22 272452288573184
*** (1) TRANSACTION:
TRANSACTION 1824, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1
MySQL thread id 13, OS thread handle 272452718423808, query id 34 localhost root updating
UPDATE accounts SET balance = balance + 200 WHERE id = 1

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 2 page no 4 n bits 72 index PRIMARY of table `testdb`.`accounts` trx id 1824 lock_mode X locks rec but not gap
...

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2 page no 4 n bits 72 index PRIMARY of table `testdb`.`accounts` trx id 1824 lock_mode X locks rec but not gap waiting
...

*** (2) TRANSACTION:
TRANSACTION 1823, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1
MySQL thread id 12, OS thread handle 272452796018432, query id 35 localhost root updating
UPDATE accounts SET balance = balance + 100 WHERE id = 2

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 2 page no 4 n bits 72 index PRIMARY of table `testdb`.`accounts` trx id 1823 lock_mode X locks rec but not gap
...

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2 page no 4 n bits 72 index PRIMARY of table `testdb`.`accounts` trx id 1823 lock_mode X locks rec but not gap waiting
...

*** WE ROLL BACK TRANSACTION (2)

LATEST DETECTED DEADLOCKは直近1件しか保持されない。次のデッドロックが発生すると上書きされるため、発生直後に確認する必要がある。

各トランザクションの読み方

デッドロックに加わった各トランザクションは*** (1) TRANSACTION:のように番号付きで並ぶ。

*** (1) TRANSACTION:
TRANSACTION 1824, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1
MySQL thread id 13, OS thread handle 272452718423808, query id 34 localhost root updating
UPDATE accounts SET balance = balance + 200 WHERE id = 1
  • TRANSACTION 1824: トランザクションID
  • MySQL thread id 13: SHOW PROCESSLISTIdと対応する接続ID
  • 最終行: 待たされているクエリそのもの

MySQL thread idSHOW PROCESSLISTのIdと同じ採番であるため、そのままセッションの特定に使える。

参考: 【MySQL】SHOW PROCESSLISTで実行中クエリとロック待ちを確認する

HOLDS THE LOCK(S)とWAITING FOR THIS LOCK TO BE GRANTED

各トランザクションには、保持しているロックと待っているロックがそれぞれ出力される。

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 2 page no 4 n bits 72 index PRIMARY of table `testdb`.`accounts` trx id 1824 lock_mode X locks rec but not gap

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2 page no 4 n bits 72 index PRIMARY of table `testdb`.`accounts` trx id 1824 lock_mode X locks rec but not gap waiting

トランザクション(1)はaccountsテーブルの主キーインデックス上のある行を排他ロック(lock_mode X)で保持しながら、別の行への排他ロックを待っている。トランザクション(2)も同様に、(1)が待っているロックを保持しつつ、(1)が保持しているロックを待っており、待ち合いの循環が成立している。

RECORD LOCKSの直後にPHYSICAL RECORDとして16進ダンプが続くが、直接読み解くのは難しい。代わりに、各トランザクションの最終行に出ているクエリのWHERE句から対象行を特定する。

ロールバックされる側

*** WE ROLL BACK TRANSACTION (2)

最後の行に、どちらが中断されたかが出る。PostgreSQLは循環を検出したトランザクション自身を中断するのに対し、InnoDBは変更した行数が少ないトランザクションをロールバック対象に選ぶ。循環を検出した側が必ず中断されるわけではない点に注意する。

innodb_print_all_deadlocksで全件をログに残す

SHOW ENGINE INNODB STATUSでは直近1件しか確認できないため、デッドロックが頻発する環境ではinnodb_print_all_deadlocksを有効にし、発生の都度エラーログへ書き出す。デフォルトはOFFである。

SET GLOBAL innodb_print_all_deadlocks = ON;

出力される内容は[Note]レベルのメッセージであり、デフォルトのlog_error_verbosity2)ではエラーログに記録されない。あわせて3に設定しておく必要がある。

SET GLOBAL log_error_verbosity = 3;

設定後は、エラーログにSHOW ENGINE INNODB STATUSと同じ内容が出力される。

2026-08-22T23:46:22.305062Z 0 [Note] [MY-012468] [InnoDB] Transactions deadlock detected, dumping detailed information.
2026-08-22T23:46:22.305209Z 0 [Note] [MY-012469] [InnoDB]  *** (1) TRANSACTION:
TRANSACTION 1824, ACTIVE 2 sec starting index read
...
2026-08-22T23:46:22.306160Z 0 [Note] [MY-012469] [InnoDB] *** WE ROLL BACK TRANSACTION (2)

Transactions deadlock detected, dumping detailed information.から始まり、各行に[MY-012469]のエラーコードが付く以外はSHOW ENGINE INNODB STATUSの出力と同じ形式である。エラーログの場所はSHOW VARIABLES LIKE 'log_error'で確認できる。

SHOW VARIABLES LIKE 'log_error';

発生回数を数える

個々のログではなく発生頻度を見るには、information_schema.INNODB_METRICSlock_deadlocksを確認する。

mysql> SELECT NAME, COUNT, STATUS FROM information_schema.INNODB_METRICS WHERE NAME = 'lock_deadlocks';
+----------------+-------+---------+
| NAME           | COUNT | STATUS  |
+----------------+-------+---------+
| lock_deadlocks | 1     | enabled |
+----------------+-------+---------+

lockサブシステムのメトリクスはデフォルトで有効(enabled)になっており、事前にinnodb_monitor_enableを実行する必要はない。COUNTはサーバ起動以降の累積値であり、定期的に取得すれば発生頻度の増減を追える。

ログから対策へ

デッドロックのログから分かるのは、循環に加わったクエリとロックの対象行である。そこから取れる対策はPostgreSQLと同様に以下へ集約される。

ロックの取得順序を揃える

複数の行をロックする処理では、取得順序をすべての処理で統一する。主キーの昇順に揃えると循環が生じなくなる。

SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;

最初から必要な強さのロックを取る

SELECTで読んでから同じ行をUPDATEする処理は、途中でロックを強い側へ昇格させる。あとで更新する予定があるなら、最初のSELECTの時点でFOR UPDATEを付けて必要な強さのロックを取る。

トランザクションを短くする

ロックの保持時間が短いほど、待ち合わせの循環が成立する余地は小さくなる。トランザクションの内側から外部APIの呼び出しなど時間のかかる処理を追い出す。

リトライする

ロールバックされたトランザクションは全体が取り消されているため、そのまま再実行できる。エラー番号1213(SQLSTATE 40001)を捕捉して数回リトライする実装を入れておく。

参考