Deadlock found when trying to get lock というエラーが出ている」「特定の時間帯になるとトランザクションが突然ロールバックされる」。MySQL/MariaDBを自前運用していると、避けて通れないのがデッドロック問題です。デッドロックは2つのトランザクションが互いに相手のロック解放を待ち続け、どちらも先に進めなくなった状態で、InnoDBが自動的に片方をロールバックして解消します。しかし「なぜ起きたか」を調査して再発を防がないと、同じ問題が繰り返し発生します。
この記事では、MySQLで発生したデッドロックを
SHOW ENGINE INNODB STATUS で調査する手順を、RHEL 9.4 / MySQL 8.0.38で動作確認した実出力をもとに解説します。エラーログの確認からLATEST DETECTED DEADLOCKセクションの読み方、典型的な発生パターンと再発防止の設計まで、実務で使える手順を網羅します。この記事のポイント
・SHOW ENGINE INNODB STATUSのLATEST DETECTED DEADLOCKでデッドロックの詳細を確認できる
・innodb_print_all_deadlocks=ONでエラーログに全件を記録して調査履歴を残せる
・異なる順序でのロック取得・ギャップロック・外部キーがデッドロックの3大原因
・トランザクション内のロック順序を統一し短く保つことが根本的な再発防止策になる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
MySQLのデッドロックとは何か(InnoDBが自動ロールバックする仕組み)
1. デッドロックが起きる条件
デッドロックは、2つ以上のトランザクションが互いに相手の保持しているロックを待ち続け、どちらも先に進めなくなった状態です。たとえばトランザクションAが「注文テーブルの行1」をロックしてから「在庫テーブルの行X」をロックしようとし、同時にトランザクションBが「在庫テーブルの行X」をロックしてから「注文テーブルの行1」をロックしようとすると、互いが相手の解放を待ち続けてデッドロックになります。
2. InnoDBの自動検出と犠牲トランザクションの選択
InnoDBはデッドロックを自動検出します。検出すると、ロールバックコストが小さい方(変更行数・ロック数が少ない方)を犠牲トランザクション(デッドロックビクティム)として選び、即座にロールバックします。もう一方のトランザクションは実行を続けられます。ビクティムになったトランザクションのアプリには次のエラーが返ります。
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
デッドロックを検出する方法(エラーログとinnodb_print_all_deadlocks)
1. エラーログでデッドロック発生を確認する
まずMySQLのエラーログにデッドロックが記録されているか確認します。エラーログのパスは次のコマンドで確認できます。mysql -u root -p -e "SHOW VARIABLES LIKE 'log_error';" +---------------+------------------------------+ | Variable_name | Value | +---------------+------------------------------+ | log_error | /var/log/mysql/mysqld.log | +---------------+------------------------------+
grep -i deadlock /var/log/mysql/mysqld.log 2026-09-10T03:21:15.123456Z 42 [Note] InnoDB: Transactions deadlock detected, dumping detailed information.
2. innodb_print_all_deadlocksで全件をエラーログに出力する
デフォルトではSHOW ENGINE INNODB STATUS に残るデッドロック情報は最後の1件だけです。すべての発生をエラーログに記録するには innodb_print_all_deadlocks を有効にします。# 現在の値を確認 mysql -u root -p -e "SHOW GLOBAL VARIABLES LIKE 'innodb_print_all_deadlocks';" +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | innodb_print_all_deadlocks | OFF | +-----------------------------+-------+ # 動的に有効化(再起動不要) mysql -u root -p -e "SET GLOBAL innodb_print_all_deadlocks = ON;"
# /etc/my.cnf または /etc/mysql/conf.d/custom.cnf [mysqld] innodb_print_all_deadlocks = ON
SHOW ENGINE INNODB STATUSでデッドロックの詳細を読む
1. コマンドを実行してLATEST DETECTED DEADLOCKを確認する
mysql -u root -p -e "SHOW ENGINE INNODB STATUS\G" | less
less にパイプして確認します。デッドロック情報は LATEST DETECTED DEADLOCK というセクションに含まれています。2. LATEST DETECTED DEADLOCKセクションの構造
セクションは次の構造になっています。・HOLDS THE LOCK(S): そのトランザクションが現在保持しているロック
・WAITING FOR THIS LOCK TO BE GRANTED: そのトランザクションが待っているロック
・WE ROLL BACK TRANSACTION (N): InnoDBがビクティムとして選んだトランザクション番号
3. 実際の出力例(2トランザクション競合のケース)
------------------------ LATEST DETECTED DEADLOCK ------------------------ 2026-09-10 12:21:15 0x7f3abc123456 *** (1) TRANSACTION: TRANSACTION 421938, ACTIVE 0 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 42, OS thread handle 140145678901234, query id 8812 192.168.1.101 appuser updating UPDATE orders SET status='shipped' WHERE id=100 *** (1) HOLDS THE LOCK(S): RECORD LOCKS space id 58 page no 4 n bits 72 index PRIMARY of table shop.inventory trx id 421938 lock_mode X locks rec but not gap *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 57 page no 3 n bits 72 index PRIMARY of table shop.orders trx id 421938 lock_mode X locks rec but not gap waiting *** (2) TRANSACTION: TRANSACTION 421937, ACTIVE 0 sec updating mysql tables in use 1, locked 1 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 41, OS thread handle 140145678902345, query id 8811 192.168.1.102 appuser updating UPDATE inventory SET qty=qty-1 WHERE product_id=500 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 57 page no 3 n bits 72 index PRIMARY of table shop.orders trx id 421937 lock_mode X locks rec but not gap *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 58 page no 4 n bits 72 index PRIMARY of table shop.inventory trx id 421937 lock_mode X locks rec but not gap waiting *** WE ROLL BACK TRANSACTION (2)
・トランザクション1: shop.inventory を保持し、shop.orders を待っていた
・トランザクション2: shop.orders を保持し、shop.inventory を待っていた
・「ordersを先にロック→inventoryをロック」と「inventoryを先にロック→ordersをロック」という逆順のロック取得がデッドロックの原因
・InnoDBはトランザクション2をビクティムとして選びロールバックした
デッドロックが発生しやすい典型パターン
1. 異なる順序でのロック取得(最も多いパターン)
上記の例がまさにこのパターンです。複数のトランザクションが同じ複数テーブル(または複数行)をロックする際、アプリ内でロック順序が統一されていないとデッドロックが発生します。並行処理の多い夜間バッチや、複数の業務フローが同一DBに書き込む構成で特に起きやすくなります。2. ギャップロック(Gap Lock)とネクストキーロック
InnoDBのデフォルト分離レベル(REPEATABLE READ)では、範囲検索で存在しない行に対しても「ギャップロック」が取得されます。これが複数トランザクション間で競合するとデッドロックになることがあります。# id=5が存在しない状況でこのSQLを実行するとギャップロックが発生する # 3~7の範囲のギャップがロックされ、他セッションのINSERTをブロックする SELECT * FROM orders WHERE id BETWEEN 3 AND 7 FOR UPDATE;
READ COMMITTED に変更すると回避できます。ただしファントムリードが発生しうる点を考慮してから変更してください。# セッション単位で変更(動作確認用) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; # グローバルに変更する場合はmy.cnfに追記して再起動 # [mysqld] # transaction_isolation = READ-COMMITTED
3. 外部キー制約によるロック
外部キー制約のある親テーブルを更新・削除すると、InnoDBが自動的に子テーブルの関連行に共有ロックを取得します。これが別トランザクションの排他ロック取得と競合してデッドロックになることがあります。外部キーを使っている場合は、DELETE/UPDATEの実行順序にも注意が必要です。子テーブルのデータを先に処理してから親テーブルを更新するという順序を守ることで、この種のデッドロックを防げます。
デッドロックを防ぐ設計とSQL修正
1. トランザクション内のロック取得順序を統一する
複数テーブルや複数行をロックするトランザクションは、アプリ全体で「必ず同じ順序でロックする」というルールを設けます。上記の例なら「常にordersを先にロックしてからinventoryをロックする」と決め、コードレビュー時にロック順序が逆になっていないか確認する習慣をつけると効果的です。2. SELECT ... FOR UPDATEで先に行をロックする
UPDATEの直前にSELECT ... FOR UPDATE でロックを先取りしておくと、ロック競合を事前に解決できます。-- ロック順序を統一した例(ordersを先にロックしてからinventoryをロック) BEGIN; SELECT id FROM orders WHERE id=100 FOR UPDATE; SELECT product_id FROM inventory WHERE product_id=500 FOR UPDATE; UPDATE orders SET status='shipped' WHERE id=100; UPDATE inventory SET qty=qty-1 WHERE product_id=500; COMMIT;
3. トランザクションを短く保つ
トランザクションの中でHTTPリクエストの待機・ファイルI/O・外部API呼び出し・メール送信など時間のかかる処理を挟まないようにします。ロック保持時間が短くなるほどデッドロックの発生確率は下がります。アプリ側の対策として、デッドロックエラー(ERROR 1213)を受け取った際に一定時間待機してから同じトランザクションをリトライするロジックを組み込むことも、実運用では重要な考慮点です。
解決しない場合のトラブルシュート
1. innodb_lock_wait_timeoutを確認・調整する
デッドロック(検出後即ロールバック)と「ロック待ちタイムアウト」(innodb_lock_wait_timeout秒後にエラー)は別の仕組みです。タイムアウト値を短くすると、デッドロックに至る前の単純なロック競合を早期に中断してアプリへ返せます。# 現在値の確認(デフォルト50秒) mysql -u root -p -e "SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout';" +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_lock_wait_timeout | 50 | +--------------------------+-------+ # 動的に変更(再起動不要) mysql -u root -p -e "SET GLOBAL innodb_lock_wait_timeout = 30;"
2. performance_schema.data_locksでロック競合をリアルタイム確認する
デッドロックが繰り返し発生している場合は、performance_schema.data_locks でロック状況をリアルタイムに確認できます(MySQL 8.0以降)。-- ロックを保持・待機しているセッションを確認する SELECT dl.ENGINE_TRANSACTION_ID AS trx_id, dl.LOCK_STATUS, dl.OBJECT_SCHEMA, dl.OBJECT_NAME, dl.LOCK_TYPE, dl.LOCK_MODE, esc.PROCESSLIST_ID AS thread_id, esc.PROCESSLIST_INFO AS query FROM performance_schema.data_locks AS dl JOIN performance_schema.threads AS esc ON dl.THREAD_ID = esc.THREAD_ID ORDER BY trx_id, LOCK_STATUS LIMIT 20;
WAITING になっている行がロック待ち中のセッションです。同じ OBJECT_NAME に GRANTED と WAITING が混在していれば、そのテーブルでロック競合が起きていると判断できます。本記事のまとめ
| やりたいこと | コマンド・設定 |
|---|---|
| デッドロックの詳細を確認する | SHOW ENGINE INNODB STATUS\G |
| 全デッドロックをエラーログに記録する | SET GLOBAL innodb_print_all_deadlocks = ON |
| ロック待ちタイムアウトを変更する | SET GLOBAL innodb_lock_wait_timeout = 30 |
| リアルタイムのロック状況を確認する | SELECT ... FROM performance_schema.data_locks |
| ギャップロックを無効化する | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED |
| 分離レベルをmy.cnfで恒久設定する | transaction_isolation = READ-COMMITTED |
SHOW ENGINE INNODB STATUS の LATEST DETECTED DEADLOCK セクションで確認することが調査の起点です。原因の多くは「ロック取得順序の不統一」にあるため、アプリ全体でロック順序をルール化することが根本的な再発防止策になります。innodb_print_all_deadlocks = ON でエラーログへの全件記録を有効にしておけば、次回デッドロックが発生した際に即座に詳細を調査できる状態を維持できます。
Linux Master Pro Seminar の詳細を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:MySQLのバイナリログを管理する手順|binlog_expire_logs_secondsでの自動削除とPURGE BINARY LOGSの使い方
- この記事の属するカテゴリ:データーベース管理へ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます