17

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

4

4 に答える 4

27

デッドロックに対処する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 ...FORUPDATEを介してアクセスするプライマリエンティティのロックを取得することです。または、複数に触れた場合は、それらすべてをロックしますが、毎回同じ順序でロックします(主キーによる順序が適切です)。

于 2009-10-05T16:50:50.263 に答える
15

ここで PostgreSQL が行うことは、Explicit Lockingに関するドキュメントで説明されています。「デッドロック」セクションの例は、おそらく何をしているのかを示しています。予想していなかった部分は、何かを UPDATE すると、関連するトランザクションが終了するまで続くその行のロックを取得することです。複数のクライアントが一度に複数の更新を行っている場合、デッドロックを防ぐために最善を尽くさない限り、デッドロックが発生することは避けられません。

UPDATE のような暗黙的なロックを取得するものが複数ある場合は、シーケンス全体を BEGIN/COMMIT トランザクション ブロックでラップし、それらがロックを取得する順序について一貫性があることを確認する必要があります (UPDATE が取得するもののような暗黙的なものであっても)。どこにでも。テーブル A の次にテーブル B の何かを更新する必要があり、アプリの一部が A の次に B を実行し、もう一方の部分が B の次に A を実行する場合、ある日デッドロックが発生します。同じテーブルに対する 2 つの UPDATE は、クライアント間で繰り返し可能な 2 つの順序を適用できない限り、同様に失敗する運命にあります。更新するレコードのセットを取得したら、主キーで並べ替え、常に「下位」のレコードを最初に取得するのが一般的な戦略です。

ここで INSERT が原因である可能性は低く、Ants が既に説明したように主キーに違反しない限り、デッドロック状態になるのははるかに困難です。

やりたくないことは、アプリでロックを複製しようとすることです。これにより、スケーラビリティと信頼性が大幅に混乱します (データベースのデッドロックが発生する可能性があります)。標準のデータベース ロック方法の範囲内でこれを回避できない場合は、代わりにアドバイザリ ロック機能または明示的なLOCK TABLEを使用して、必要なものを強制することを検討してください。これにより、すべてのロックをクライアント側にプッシュしようとする面倒なコーディングの世界を救うことができます。テーブルに対して複数の更新があり、それらが発生する順序を強制できない場合は、実行中にテーブル全体をロックするしかありません。これは、デッドロックの可能性をもたらさない唯一のルートです。

于 2009-10-06T06:06:21.680 に答える
6

デッドロックの説明:
簡単に言えば、特定の SQL ステートメント (INSERT など) が、データベースの特定の部分のロックを解放する別のステートメントを待ってから、続行できるようになっているということです。このロックが解除されるまで、最初の SQL ステートメント (「ステートメント A」と呼びます) は、データベースのこの部分にアクセスしてジョブを実行することを許可しません (= 通常のロック状況)。しかし...ステートメントAは、データベースの他のユーザーがアクセスできないようにするために、データベースの別の部分にもロックをかけています(ロックの種類に応じて、読み取り、または変更/削除のため)。さて... 2 番目の SQL ステートメント自体が、ステートメント A のロックによってマークされたデータ セクションにアクセスする必要があります。

救済策は...

これには、これらのさまざまなスレッドが実行している特定の SQL ステートメントを知る必要があり、次のいずれかの方法があるかどうかを調べる必要があります。

a) ロックの一部を削除するか、タイプを変更します。
   たとえば、テーブル全体がロックされている可能性があります。これにより、特定の行のみがロックされます。
   そのページが必要になります。
b) これらのクエリが一度に複数送信されるのを防ぐ。
   これは、
   マルチスレッドロジック。

「b)」アプローチが正しく実装されていない場合、デッドロック状況が SQL 内からプログラム/スレッド ロジック内に移動する可能性があることに注意してください。重要なのは、これらのデッドロックが発生しやすいクエリのいずれかを実行しようとしているスレッドが最初に取得するミューテックスを 1 つだけ作成することです。

于 2009-10-05T14:38:31.280 に答える
0

あなたの問題は、おそらく、挿入コマンドが一方または両方のインデックスをロックしようとしていて、インデックスがもう一方の踏み板に対してロックされていることです。

よくある間違いの 1 つは、スレッドごとに異なる順序でリソースをロックすることです。順序を確認し、すべてのスレッドで同じ順序でリソースをロックしてみてください。

于 2009-10-05T14:37:03.343 に答える