Deadlock found(デッドロック)
「Deadlock found when trying to get lock」で片方のトランザクションが強制ロールバックされる仕組みと、ロック取得順序の統一・リトライで安全に再発を止める勘所を持ち帰れる。
- 複数トランザクションが互いの保持ロックを待ち循環待機(cycle)に陥った状態。InnoDBは待ちグラフで検知し、片方を即座に犠牲(victim)に選んでロールバックし、残りを進める。
- 主因は複数行を触る順序の不一致で、ギャップロックや長いトランザクションも競合を増やす。まずSHOW ENGINE INNODB STATUSのLATEST DETECTED DEADLOCKを読む。
- 恒久対策はアプリ側で行の取得順序を主キー昇順などに統一すること。デッドロックはゼロにできないため、犠牲になった側を短い待機を挟んで自動リトライする設計を必ず併用する。
一言でいうと(このエラーが何を意味するか)
Deadlock found when trying to get lock; try restarting transaction(MySQL/InnoDB のエラー番号 1213、SQLSTATE 40001)は、2つ以上のトランザクションが互いに相手の保持するロックを待ち、永遠に進めなくなったことを DB が検知し、そのうち1つを強制的に巻き戻して膠着を破ったという通知です。
デッドロックは「循環待機(circular wait)」です。トランザクション T1 がロック A を持ったまま B を要求し、同時に T2 が B を持ったまま A を要求すると、両者は相手の解放を待ち続けます。InnoDB は各ロック待ちを有向辺とする待ちグラフ(wait-for graph)を持ち、辺を張った瞬間に閉路を探索します。閉路が見つかれば、そのトランザクションの1つを犠牲者(victim)に選んでロールバックし、他方を進行させます。
Lock wait timeout exceeded(エラー1205)とデッドロックは異なります。1205 は循環がなくても、単に先行トランザクションのロック解放を innodb_lock_wait_timeout(既定50秒)まで待って諦めた状態です。1213 は循環を実際に検知した即時のロールバックで、待たされません。両者は混同されがちですが、原因の切り分けが変わります。
横にスクロール
よくある原因(複数を具体的に、頻度順)
1. 複数行を触る順序の不一致(最頻)。2つのトランザクションが同じ2行 A, B を更新するとき、片方が A→B、もう片方が B→A の順にロックを取ると循環が生じます。送金処理で「口座番号の小さい方から引く」といった規約がないコードで典型的に起こります。ロックは行ごとに、その行を更新(または SELECT ... FOR UPDATE)した順に取得されるため、経路が交差した瞬間にデッドロックの窓が開きます。
2. ギャップロックによる想定外の競合。InnoDB の既定分離レベル REPEATABLE READ では、範囲更新やユニークでないインデックス経由の更新が、実在する行だけでなく行と行の間の隙間(ギャップ)にもロックをかけます。異なる値を挿入する2つの INSERT が同じギャップロックを共有取得した後、互いに挿入意図ロック(insert intention lock)へ昇格しようとして詰まる、という競合が起こります。「別々の行なのに衝突する」場合はこれを疑います。
3. 長いトランザクション。ロックを長時間保持するほど、他とロック範囲が重なる確率が上がります。トランザクション内でのアプリ側の待機(外部API呼び出し、ユーザー入力待ち)、巨大な範囲更新、コミット忘れが典型です。保持時間が延びると 1205 も 1213 も両方増えます。
4. インデックス不足による広すぎるロック。更新の WHERE に使えるインデックスがないと、InnoDB は行を絞り込めず走査した多数の行にロックをかけ、本来無関係な行同士が競合してデッドロック確率が跳ね上がります。
| 症状 | エラー番号 | 循環の有無 | 主な原因 |
|---|---|---|---|
| Deadlock found … | 1213 (40001) | あり(検知して即ロールバック) | 取得順序の不一致・ギャップロック |
| Lock wait timeout … | 1205 | なし(時間切れ) | 長いトランザクション・ホットロウ競合 |
診断の手順(切り分け方・見るべきログ/コマンド)
まず直近のデッドロックを見る。InnoDB は最後に検知したデッドロックの詳細を保持しています。
SHOW ENGINE INNODB STATUS;
出力の LATEST DETECTED DEADLOCK セクションに、関与した2つのトランザクション(TRANSACTION 1, TRANSACTION 2)それぞれの実行中 SQL、保持ロック(HOLDS THE LOCK(S))、待機ロック(WAITING FOR THIS LOCK)、そして WE ROLL BACK TRANSACTION (n)(どちらを犠牲にしたか)が記録されます。保持ロックと待機ロックのインデックス名・ロック種別(X, S, gap, insert intention)を突き合わせれば、どの行・ギャップで循環したかが特定できます。
SHOW ENGINE INNODB STATUS が保持するのは「最新の1件」だけで、上書きされます。継続的に記録したい場合は次を有効化し、サーバーのエラーログに全デッドロックを吐かせます。
SET GLOBAL innodb_print_all_deadlocks = ON;
発生時点の全ロック状況を見る。現在どのトランザクションがどのロックを保持・待機しているかは、performance_schema から確認できます(MySQL 8.0 以降)。
SELECT * FROM performance_schema.data_lock_waits;
SELECT * FROM performance_schema.data_locks;
data_lock_waits は「待っている側(requesting)」と「待たせている側(blocking)」の内部トランザクション ID を対にして返すため、待ちグラフの辺を直接読めます。さらに information_schema.innodb_trx で問題トランザクションの経過時間や実行 SQL を掘り下げます。
SELECT trx_id, trx_state, trx_started, trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started;
再現とロック粒度の確認。疑わしい更新文の前に EXPLAIN をかけ、想定したインデックスが使われているか(type が range/ref か、ALL の全走査でないか)を確認します。全走査ならロックが広がりデッドロックの温床になります。
PostgreSQL のデッドロックはメッセージ・機構とも別です。既定では即時検知ではなく deadlock_timeout(既定1秒)だけ待ってから待ちグラフを確認し、循環があれば ERROR: deadlock detected(SQLSTATE 40P01)を返します。詳細は log_lock_waits を有効にしてサーバーログで追います。「デッドロック検知は必ず即時」という思い込みは製品差で崩れます。
解決と予防(対処と再発防止)
行の取得順序を統一する(最重要)。複数行を更新するトランザクションは、常に同じ順序(主キーの昇順など、全アプリで一貫した規約)でロックを取ります。これにより待ちグラフに閉路が生じ得なくなり、原因1のデッドロックは原理的に消えます。
-- 悪い例: 呼び出し側次第で 100→200 と 200→100 が混在し循環し得る
UPDATE accounts SET balance = balance - 50 WHERE id = :from;
UPDATE accounts SET balance = balance + 50 WHERE id = :to;
-- 良い例: id の小さい行から必ずロックし、順序を固定する
SELECT id FROM accounts
WHERE id IN (:from, :to)
ORDER BY id
FOR UPDATE;
-- 以降で残高を更新する
必ずリトライを実装する。順序を統一しても、ギャップロックや実行計画の変化で残るデッドロックはゼロにできません。1213 を受けたらそのトランザクション全体を巻き戻して、短いランダム遅延(指数バックオフ)を挟み再実行します。エラーが「try restarting transaction」と促すのはこのためで、犠牲者側は既にロールバック済みなので安全に最初からやり直せます。
import time, random
for attempt in range(3):
try:
run_transaction() # BEGIN 〜 COMMIT を丸ごと
break
except DeadlockError: # MySQL 1213 / SQLSTATE 40001
if attempt == 2:
raise
time.sleep(random.uniform(0.05, 0.2) * (attempt + 1))
トランザクションを短く保つ。ロック取得はできるだけコミット直前に寄せ、トランザクション内に外部 API 呼び出しやユーザー入力待ちを入れない。処理と DB ロックの保持時間を切り離すほど競合窓が縮みます。
ロック粒度を狭める。更新の WHERE に効くインデックスを用意して走査行数を最小化し、余計な行・ギャップをロックしない。ギャップロックが問題で非反復読み取りを許容できるなら、分離レベルを READ COMMITTED に下げるとギャップロックが原則消えてデッドロックが減る場合があります(許容可否を必ず評価してから)。
- デッドロックの必要条件(相互排他・保持と待機・横取り不可・循環待機)のうち、実務で崩しやすいのは「循環待機」=取得順序の統一
- InnoDB は待ちグラフの閉路を検知し、更新(insert/update/delete)した行数が少ない側(=巻き戻しコストが小さい方)を犠牲者に選ぶ
- 1213(デッドロック検知・即時ロールバック)と 1205(ロック待ちタイムアウト・時間切れ)は別物
- REPEATABLE READ のギャップロックが別々の行の INSERT でもデッドロックを生む
デッドロックの土台にあるロックとトランザクション分離の原理はデータベースを、循環待機を含む一般の並行制御と資源割り当ての理論はOSを、リトライやバックオフを堅牢に組む実装作法はプログラミングを参照してください。
エラー辞典の記事ガイド
Deadlock found(デッドロック)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
データベース
比較で見る軸
難易度: advanced / カテゴリ: エラー辞典 / タグ数: 6
導入後に効く点
主因は複数行を触る順序の不一致で、ギャップロックや長いトランザクションも競合を増やす。まずSHOW ENGINE INNODB STATUSのLATEST DETECTED DEADLOCKを読む。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- エラー辞典
- タグ数
- 6
判断チェックリスト
- 自社の用途が「データベース / トランザクション」に近いか確認する。
- 強みである「複数トランザクションが互いの保持ロックを待ち循環待機(cycle)に陥った状態。InnoDBは待ちグラフで検知し、片方を即座に犠牲(victim)に選んでロールバックし、残りを進める。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。