システムやデータベースを扱うエンジニアにとって、避けては通れない問題のひとつがデッドロックです。
複数のプロセスやトランザクションが互いにリソースを待ち続け、永遠に処理が進まなくなるこの状態は、システム障害の原因となることもあります。
「デッドロックってどんな状態?」「なぜ起きるの?」という疑問をお持ちの方に向けて、この記事ではデッドロックの意味・仕組み・具体例をわかりやすく解説していきます。
デッドロックとは複数のプロセスが互いのリソース解放を待ち続ける膠着状態のことである
それではまず、デッドロックの基本的な定義と仕組みについて解説していきます。
デッドロックとは、2つ以上のプロセスまたはトランザクションが互いに相手の保持するリソースを待ち続け、どちらも処理を完了できない状態のことを指します。
まるで二人が狭い廊下で向かい合い、互いに「相手が先によけるべき」と主張して誰も動けない状況に例えることができます。
デッドロックが成立するためには、以下の4つの条件が全て同時に満たされる必要があります。
①相互排除(Mutual Exclusion):リソースは一度に1つのプロセスしか使用できない。②占有と待機(Hold and Wait):リソースを保持したまま他のリソースを待機する。③非横取り(No Preemption):他のプロセスのリソースを強制的に奪えない。④循環待機(Circular Wait):プロセスが円環状に互いのリソースを待つ状態。
この4条件はコンピュータ科学者のエドガー・ダイクストラが定義したもので、デッドロック発生の必要十分条件として広く知られています。
データベースにおけるデッドロックの仕組み
続いては、特に発生頻度の高いデータベースにおけるデッドロックの仕組みと具体例について確認していきます。
トランザクションとロックの関係
データベースでは、複数のユーザーが同時にデータを読み書きする際にデータの整合性を保つためにトランザクションとロックが使用されます。
あるトランザクションがテーブルの特定の行をロックすると、他のトランザクションはそのロックが解放されるまで同じ行にアクセスできません。
この仕組みがデッドロックの温床となることがあります。
データベースデッドロックの具体例
データベースデッドロックの典型例
トランザクションA:テーブル1の行Xをロック → 次にテーブル2の行Yをロックしようとする
トランザクションB:テーブル2の行Yをロック → 次にテーブル1の行Xをロックしようとする
結果:AはYのロック解放を待ち、BはXのロック解放を待つ → 永久に解消されない循環待機が発生
デッドロック検知と自動解消
多くのデータベース管理システム(DBMS)は、デッドロックを自動的に検知して解消する機能を備えています。
MySQLやPostgreSQL・Oracleなどでは、デッドロックを検知すると一方のトランザクションを強制的にロールバックし、他方が処理を継続できるようにします。
ロールバックされたトランザクションはエラーを受け取るため、アプリケーション側でのリトライ処理の実装が重要です。
オペレーティングシステムにおけるデッドロック
続いては、OSレベルのプロセス管理におけるデッドロックについて確認していきます。
プロセスとリソース競合
OSでは複数のプロセスが同時に動作し、CPU・メモリ・ファイル・ネットワークソケットなどのリソースを共有します。
プロセスAがファイルAをロックしてファイルBを待ち、プロセスBがファイルBをロックしてファイルAを待つ状況が生じると、OSレベルのデッドロックが発生します。
排他制御と同期の問題
マルチスレッドプログラミングでは、ミューテックスやセマフォを使った排他制御が必要ですが、不適切な実装がデッドロックを引き起こすことがあります。
スレッドAがミューテックスMを取得後にミューテックスNを取得しようとし、スレッドBがミューテックスNを取得後にミューテックスMを取得しようとするパターンが最も典型的なスレッドデッドロックです。
ライブロックとスタベーション
デッドロックに似た概念として「ライブロック」と「スタベーション」があります。
ライブロックはプロセスが動き続けながらも前進しない状態であり、スタベーションはあるプロセスが永遠にリソースを割り当てられない状態を指します。
これらはデッドロックとは異なる問題ですが、同様にシステムの正常動作を妨げる可能性があります。
デッドロックの検知・予防・回避の手法
続いては、デッドロックに対処するための代表的な手法について確認していきます。
デッドロックの予防
デッドロックを根本的に防ぐには、前述の4条件のいずれかを排除することが基本です。
たとえば循環待機を防ぐために、全てのロックを一定の順序で取得するルールを設けることが効果的です。
データベースでは、同じテーブルへのアクセス順序を統一することがデッドロック予防の基本的な対策となります。
タイムアウトによるデッドロック解消
ロック待機にタイムアウトを設定し、一定時間経過後にトランザクションを自動的に中断する方法です。
タイムアウト値の適切な設定がポイントとなり、短すぎると正常な処理まで中断してしまい、長すぎると応答性が低下します。
ウェイト・フォーグラフによる検知
ウェイト・フォーグラフとはプロセス間の待機関係をグラフで表現したものであり、グラフ上に閉路(サイクル)が存在する場合にデッドロックが発生していると判定できます。
DBMSはこのグラフを定期的に検査し、閉路を検出した際に犠牲トランザクション(Victim)を選択してロールバックすることでデッドロックを解消します。
まとめ
デッドロックは複数のプロセスやトランザクションが互いのリソース解放を待ち続ける膠着状態であり、4つの発生条件が全て揃ったときに生じます。
データベースでは自動検知・ロールバックによる解消機能が備わっていますが、アプリケーション側でのリトライ処理とロック順序の統一が根本的な予防策となります。
OSやマルチスレッドプログラミングにおいても同様の問題が起きるため、設計段階からデッドロックを意識した実装を心がけることが重要でしょう。
デッドロックの仕組みを理解することで、より堅牢なシステム設計と障害対応能力の向上につながります。