デッドロック
Deadlock
複数の処理が、相手の保持するロック等の解放を循環して待ち、介入なしでは先へ進めなくなる状態です。DBは検出して一方を中断する場合があり、システム全体が永久停止するとは限りません。
🐾 猫で例えると?
茶トラが赤いおもちゃ、アメショが青いおもちゃを押さえ、二匹とも「相手のおもちゃをもらうまで自分の物は放さない」と待つ場面を想像します。この「一つを持ったまま、相手の物を待つ輪」がデッドロックです。写真の相打ちは、単なる停止と区別して考えるきっかけになります。
💻 データベース運用における「デッドロック」とは?
システムの開発やデータベースの運用において、デッドロックは非常に厄介なクリティカルエラーの一つです。例えば、処理Aが「データ1」をロック(他から変更できないように占有)し、処理Bが「データ2」をロックしたとします。その後、処理Aがさらに「データ2」の書き換えを要求し、同時に処理Bが「データ1」の書き換えを要求した場合に発生します。
処理AはBのロックを待ち、BはAのロックを待つため、どちらも通常の処理では解放まで進めません。単に一方が処理中で他方が待っているロック待ちとは異なります。DBの検出機構は、この循環を見つけて解消します。
⚠️ デッドロックの仕組みと注意点
データベース管理システム(RDBMS)の多くは、デッドロックを自動的に検知する機構(デッドロック検出器)を備えています。検知された場合、システムはどちらか一方の処理を強制的にエラー(ロールバック)として終了させ、もう一方の処理を優先して進めることで全体の停止を回避します。
トランザクションの設計ミスが原因に
更新順序の食い違いは代表的な原因ですが、実際のロックはインデックスや制約、分離レベルにも関係します。デッドロックのログから、どのトランザクションが何を保持し、何を待っていたかを確認します。
-- デッドロックが発生しやすい危険なSQLの実行パターンの例
-- 【処理A】
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1; -- ① id=1をロック
UPDATE accounts SET balance = balance + 1000 WHERE id = 2; -- ③ id=2の解放を待つ(デッドロック!)
-- 【処理B(処理Aと同時期に実行)】
START TRANSACTION;
UPDATE accounts SET balance = balance - 500 WHERE id = 2; -- ② id=2をロック
UPDATE accounts SET balance = balance + 500 WHERE id = 1; -- ④ id=1の解放を待つ(デッドロック!)
これは独立した二つのDB接続で①②③④の順に実行したときの概念例です。最初から一つの接続で順に貼り付けても同じ状態にはなりません。検証用DBで行い、中断したトランザクションはロールバックしてから必要な処理全体を再試行します。
🛠️ デッドロックを賢く防ぐためのポイント
デッドロックは完全にゼロにすることは難しいものの、設計段階の工夫によって発生確率を大幅に下げることができます。
- リソースへのアクセス順序を統一する: 複数のデータを更新する際は、どの処理からでも必ず同じ順番(例:主キーの昇順)でアクセスするように実装し、待ち合いのループを根本から防ぎます。
- トランザクションの時間を最小限に抑える: データをロックしている時間をできるだけ短くするため、不要な処理をトランザクション内に含めず、クエリを実行したら速やかに確定(コミット)させます。
- 再試行を設計する: デッドロックによる中断を識別し、回数と待ち時間を制限してトランザクション全体を再実行します。決済などDB外の処理を二重実行しない設計も必要です。
互いに手を出したまま止まる2匹のように、待ち合いの輪ができると処理は進みません。ロックを取る順序と保持時間を見直し、DBが一方を中断した場合の扱いも決めましょう。再試行する場合は、同じ処理を重ねても支障がないか確認が必要です。