マルチスレッド - データベースのデッドロックの回避と対処
-
18-09-2019 - |
質問
Java 6 アプリケーション内からデータベースのデッドロックに対処するための良い戦略を探しています。複数の並列スレッドが同じテーブルに同時に書き込む可能性があります。データベース (Ingres RDMBS) は、デッドロックを検出すると、セッションの 1 つをランダムに強制終了します。
次の要件を考慮した場合、デッドロック状況に対処するための許容可能な手法は何でしょうか?
- 合計経過時間は、合理的に可能な限り小さく保つ必要があります
- セッションを殺すと、重要な(測定可能な)ロールバックが発生します
- 時間スレッドには方法がありません
互いに通信する、つまり戦略は自律的でなければなりません
これまでのところ、私が思いついた戦略は次のようなものです。
short attempts = 0;
boolean success = false;
long delayMs = 0;
Random random = new Random();
do {
try {
//insert loads of records in table 'x'
success = true;
} catch (ConcurrencyFailureException e) {
attempts++;
success = false;
delayMs = 1000*attempts+random.nextInt(1000*attempts);
try {
Thread.sleep(delayMs);
} catch (InterruptedException ie) {
}
}
} while (!success);
何らかの方法で改善することはできますか?例えば一定量 (マジックナンバー) の秒間待機します。より良い結果を生み出す別の戦略はあるのでしょうか?
注記: 実際にはデッドロックが非常にまれに発生することを保証するために、いくつかのデータベース レベルの手法が使用されます。また、アプリケーションは、同じテーブルに同時に書き込むスレッドのスケジュールを回避しようとします。上記の状況はあくまで「最悪のシナリオ」になります。
注記: レコードが挿入されるテーブルはヒープ パーティション テーブルとして編成され、インデックスはありません。各スレッドは独自のパーティションにレコードを挿入します。
解決
一般的に使用されるアプローチは、何らかの形式の指数関数的バックオフです。あなたではなく 1000*attempts+random
このアプローチでは、遅延を試行回数の指数関数にします。これにより、最初の 1 ~ 2 回の試行では遅延が最小限に抑えられます (デッドロックになったのは運が悪かっただけかもしれません)。しかし、後で接続が実際に混雑していることが明らかになると、はるかに大きな遅延が発生します。
もちろん、デッドロックが発生しにくくなるようにデータベース アクセスを調整するという別のアプローチもあります。しかし、クエリが何を行うのか (そしていつどのように実行されるのか) が分からなければ、それが可能かどうかを判断することはできません。
他のヒント
それが私たちのやり方でした。トランザクションが完了するまでループして再試行します。
ランダムな遅延を引き起こすことはありませんでした。
また、内部でコミットを実行しました。 try
ブロックと例外ハンドラーでのロールバック。
複数のロック可能なリソースと複数の同時トランザクションがある場合、デッドロックは避けられません。これは、ロックの競合の論理的な結果です。
ロックの競合 (つまり、悲観的なテーブルレベルのロック) を回避すると、同時実行性も妨げられる傾向があります。ロックを競合しないトランザクションを定義できれば、デッドロックを回避できます。ただし、同じテーブルへの同時アクセスはほぼデッドロックの定義です。
ロード時、挿入 (特に HEAP テーブルで) は (多くの場合) 多くの競合の問題を引き起こすことなく並行して進めることができます。インデックスの構築が遅れると、挿入中に他の更新は行われなくなります。
したがって、インデックスを削除し、編成をヒープに変更し、複数の同時プロセス (またはスレッド、通常は複数のプロセスを持つ方が高速です) でロードしてからインデックスを構築し (場合によってはテーブルを再編成する) ことで回避できる可能性があります。デッドロックを回避できるかもしれません。
更新または削除を行う場合、あまり役に立ちません。
データベースに同時アクセスする必要がない場合、簡単な解決策は、データベースを削除し、代わりにタスク処理キューを使用してデータベースを更新し、キューを介してデータベースへのアクセスをシリアル化することです。これはアプリケーションに非同期要素を導入するため、ユーザーが開始するほとんどのアプリケーションやオンライン Web アプリケーションには適していませんが、バッチ/オフライン タイプのアプリケーションには検討する価値があるかもしれません (おそらくあなたの探している答えではないことは承知しています)ただし)。
Ingres のようなデータベースでは、常にデッドロックが発生するため、挿入、更新、削除はすべて失敗することを想定し、(例のように) 再試行戦略を用意する必要があります。競合が最小限に抑えられ、デッドロックがほとんど発生しないようにデータベースを設計する必要があります。何度か再試行してもトランザクションが引き続き失敗する場合は、大規模なデータベースの再設計を行う必要がある (または、適切な使用方法でデッドロックを回避するアプリケーションを設計できる Oracle のようなシステムに移行する) 必要があることを示しています。行レベルのロックの)。
これはどのように ?
short attempts = 0;
boolean success = false;
long delayMs = 0;
Random random = new Random();
do {
try {
synchronized(ClassName.class) {
//insert loads of records in table 'x'
}
success = true;
} catch (ConcurrencyFailureException e) {
attempts++;
success = false;
delayMs = 1000*attempts+random.nextInt(1000*attempts);
try {
Thread.sleep(delayMs);
} catch (InterruptedException ie) {
}
}
} while (!success);