デッドロック

はじめに

前回、共有ロック・専有ロックについて書きましたので、デッドロックについて書いてみます。

デットロックとは

データベースの同時処理(並行処理)でよく起こるトラブルの一つです。
「お互いが相手のロック解除を待ち続けて、永遠に止まってしまう状態」のことです。

データベースでは、複数の人(またはプログラム)が同じデータを同時に扱うことがあります。
このとき、データを壊さないように「ロック」をかけます(🔒)。
それでは、お互いに違うものをロックしていて、次のロックを取りたいのに相手が持っているとどうなるでしょう?

お互いに待ち続け、永遠に処理が終わらなくなります。
これが デッドロック(Deadlock) です。

デッドロックのイメージ図(Deadlock)

お互いが相手のロック解除を待ち続け、処理が永遠に進まなくなる状態を図解しています。

🔒 共有ロック(S) 🔐 専有ロック(X) ⏳ 待ち(待機中)

循環待ちの発生(典型パターン)

トランザクションA

① Table X を ロック(X)
Table Y を取りたい…(待ち)

リソース:Table Y

🔐 専有ロック(Bが保持)

リソース:Table X

🔐 専有ロック(Aが保持)

トランザクションB

① Table Y を ロック(X)
Table X を取りたい…(待ち)
A は Y を、B は X を待っており、互いに相手が解放するまで進めず 循環待ち が成立 → デッドロック

なぜ起きる?どう防ぐ?

ポイント内容
発生条件(4つが揃うと危険) ① 相互排他 / ② 保持しつつ待つ / ③ 奪い取れない / ④ 循環待ち(A⇄B…)
設計で防ぐ ロック取得順序を統一(常に X→Y の順など)
トランザクションを短く(早くコミット)
運用で和らげる ・DBのデッドロック検出を活用(片方を中止)
リトライ処理を実装(自動再実行)
読み取りと書き込み 共有ロック(S) は同時読み取りOK / 書き込みNG
専有ロック(X) は読み書きともに他者NG

タイムラインで見る(例)

※ 左→右に時間が進みます

A B X ロック保持 Y を待機 Y ロック保持 X を待機 DEAD

デッドロックが起きる条件は

  • 相互排他:同時に使えないリソース(ロック)
  • 保持と待ち:ロックを持ちながら別のロックを待つ
  • 奪い取れない:他人のロックを強制解除できない
  • 循環待ち:AがBを待ち、BがAを待つ(ぐるぐる)

となります。

対策

デッドロックを防ぐ対策は以下の通りとなります。

対策方法内容
ロックの順番を統一するすべての処理で「同じ順番」でロックを取る(A→Bの順など)
トランザクションを短くする長時間ロックを保持しない(できるだけ早くコミット)
デッドロック検出機能を使う多くのDB(MySQL、PostgreSQLなど)は自動的に検出して一方を中止
リトライ処理を組み込むデッドロック時に再実行するようプログラムで対応

まとめ

デッドロックとは、お互いが譲らず、永遠に順番を待つ状態です。