3秒でわかる
複数の処理が互いの持つロックの解放を待ち合い、どちらも進めなくなる状態。DBは輪を検出して片方を巻き戻し、自動で解消します。
もう少し詳しく
どういうものか
デッドロックは、2つ以上の処理が互いに相手の持っているロックの解放を待ち、どちらも永久に進めなくなる状態です。トランザクションAが行Xを押さえて行Yを待ち、トランザクションBが行Yを押さえて行Xを待つと、待ち合いが輪になって止まります。DBだけでなく、スレッドとミューテックスの間でも同じことが起きます。
なぜ必要か
避けるべき現象ではありますが、仕組みを知っておく理由があります。多くのRDBMSは待ち合いの輪を検出すると、片方を犠牲者として自動でロールバックし、Deadlock found のようなエラーを返します。つまりアプリ側には「たまに失敗するトランザクション」として現れます。この性質を知らないと、再現しないエラーとして放置されます。
具体例
-- <a href="/glossary/session" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">セッション</a>1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 行1をロック
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 行2を待つ
-- セッション2 (ほぼ同時)
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 2; -- 行2をロック
UPDATE accounts SET balance = balance + 50 WHERE id = 1; -- 行1を待つ → 輪が閉じるセッション1 ─ 行1所有 ─→ 行2待ち
↑ │
└──── 行1待ち ── セッション2 (行2所有)つまずきやすいところ
対策として最も効くのは、更新する行の順序をアプリ全体で統一することです。上の例も、常にidの小さい行から更新すると輪ができません。次に効くのはトランザクションを短く保つことで、途中で外部APIを呼ぶような処理はロックの外に出します。また、デッドロックは仕様上ゼロにできないため、該当エラーを捕まえて数十ミリ秒後に1回やり直す再試行を入れておきます。
似た用語との違い
ロック待ちは、待っていればいずれ相手が解放するので進みます。デッドロックは待ち合いが輪になっているため、外から介入しない限り解けません。ライブロックは互いに譲り合って動き続けるのに前へ進まない状態を指します。
覚え方
デッドロックが成立するには4つの条件がそろう必要があり、そのうち1つでも崩せば起きません。実務で崩しやすいのは「待ちが輪になること」で、更新順序をそろえるだけで多くの事例が消えます。
ログに残る犠牲者側の情報から、どの2つの処理が衝突したのかをたどれます。