Web ベースのアプリケーションがあります。アプリケーションには、完了までに時間がかかる時間制限のあるデータベース操作 (INSERT および UPDATE) があるため、この特定のフローは Java スレッドに変更され、データベース操作全体が完了するまで待機 (ブロック) しなくなりました。
私の問題は、この特定のフローに複数のユーザーが遭遇すると、PostgreSQL によってスローされる次のエラーが発生することです。
org.postgresql.util.PSQLException: ERROR: deadlock detected
Detail: Process 13560 waits for ShareLock on transaction 3147316424; blocked by process 13566.
Process 13566 waits for ShareLock on transaction 3147316408; blocked by process 13560.
上記のエラーは、INSERT ステートメントで常にスローされます。
追加情報:1) このテーブルには PRIMARY KEY が定義されています。2) このテーブルには FOREIGN KEY 参照があります。3) 各 Java スレッドに個別のデータベース接続が渡されます。
テクノロジーWeb サーバー: Tomcat v6.0.10、Java v1.6.0、サーブレット データベース: PostgreSQL v8.2.3、接続管理: pgpool II
ベストアンサー1
デッドロックに対処する方法の 1 つは、ランダムな間隔で待機し、トランザクションを再度実行しようとする再試行メカニズムを用意することです。ランダムな間隔が必要なのは、衝突するトランザクションが継続的に互いにぶつかり合って、デバッグがさらに厄介になるライブロックと呼ばれる状態が発生しないようにするためです。実際、ほとんどの複雑なアプリケーションでは、トランザクションのシリアル化の失敗を処理する必要があるときに、遅かれ早かれこのような再試行メカニズムが必要になります。
もちろん、デッドロックの原因を特定できれば、それを排除するか、意思結局、後で痛い目に遭います。ほとんどの場合、デッドロック状態がまれな場合でも、ロックを決定論的な順序で取得したり、より粒度の粗いロックを取得したりするために、スループットとコーディングのオーバーヘッドを少し増やす価値はあります。同時実行性を拡張するときに、時折発生する大きなレイテンシ ヒットや突然のパフォーマンス低下を回避するためです。
2 つの INSERT ステートメントが常にデッドロックする場合は、一意のインデックス挿入順序の問題である可能性が高くなります。たとえば、2 つの psql コマンド ウィンドウで次の操作を試してください。
Thread A | Thread B
BEGIN; | BEGIN;
| INSERT uniq=1;
INSERT uniq=2; |
| INSERT uniq=2;
| block waiting for thread A to commit or rollback, to
| see if this is an unique key error.
INSERT uniq=1; |
blocks waiting |
for thread B, |
DEADLOCK |
V
通常、この問題を解決する最善の策は、そのようなすべてのトランザクションを保護する親オブジェクトを見つけることです。ほとんどのアプリケーションには、ユーザーやアカウントなど、この目的に適したプライマリ エンティティが 1 つまたは 2 つあります。その後、すべてのトランザクションで、SELECT ... FOR UPDATE を介して、アクセスするプライマリ エンティティのロックを取得するだけで済みます。または、複数のエンティティにアクセスする場合は、すべてのエンティティのロックを毎回同じ順序で取得します (プライマリ キーによる順序付けが適切です)。