MySQLのデッドロックを調査する手順|SHOW ENGINE INNODB STATUSで原因を特定してロック競合を解消する

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Linuxtipsデーターベース管理 > MySQLのデッドロックを調査する手順|SHOW ENGINE INNODB STATUSで原因を特定してロック競合を解消する
「アプリのログに 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大原因
・トランザクション内のロック順序を統一し短く保つことが根本的な再発防止策になる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は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 | +---------------+------------------------------+

エラーログで "Deadlock" を検索します。

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;"

再起動後も有効にするには my.cnf に追記します。

# /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;

LOCK_STATUSが WAITING になっている行がロック待ち中のセッションです。同じ OBJECT_NAMEGRANTEDWAITING が混在していれば、そのテーブルでロック競合が起きていると判断できます。

本記事のまとめ

やりたいこと コマンド・設定
デッドロックの詳細を確認する 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
MySQLのデッドロックは「どのトランザクションが、どのロックを保持し、何を待っていたか」を SHOW ENGINE INNODB STATUS の LATEST DETECTED DEADLOCK セクションで確認することが調査の起点です。原因の多くは「ロック取得順序の不統一」にあるため、アプリ全体でロック順序をルール化することが根本的な再発防止策になります。

innodb_print_all_deadlocks = ON でエラーログへの全件記録を有効にしておけば、次回デッドロックが発生した際に即座に詳細を調査できる状態を維持できます。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアがデッドロック対処を含むMySQLの実践運用スキルを指導します。
Linux Master Pro Seminar の詳細を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。