5

Devart と Entity Framework を使用してアクセスする Oracle データベースがあります。

IMPORTJOBScolumnという名前のテーブルがありますSTATUS

また、同時に複数のプロセスを実行しています。IMPORTJOBSそれぞれが status を持つ最初の行を読み取り、'REGISTERED'それを status に置き'EXECUTING'、完了したらstatus に置きます'EXECUTED'

これらのプロセスが並行して実行されているため、次のことが発生する可能性があると考えています。

  • プロセス A は、ステータスを持つ行 10 を読み取りREGISTERED
  • REGISTEREDプロセス B は、ステータスがまだある行 10 も読み取ります。
  • プロセス A は、行 10 を status に更新しますEXECUTING

プロセス A がすでに行 10 を読み取っており、そのステータスを更新しようとしているため、プロセス B は行 10 を読み取れないはずです。

これをどのように解決すればよいですか?読み取りと更新をトランザクションに入れますか? または、バージョン管理のアプローチなどを使用する必要がありますか?

ありがとう!

編集:受け入れられた回答のおかげで、私はそれを機能させ、ここに文書化しました: http://ludwigstuyck.wordpress.com/2013/02/28/concurrent-reading-and-writing-in-an-oracle-database

4

3 に答える 3

2

各プロセスはSELECT ... FOR UPDATE、読み取り時に行をロックするために を発行できます。このシナリオでは、プロセス A は行を読み取ってロックし、プロセス B は行を読み取ろうとし、プロセス A がトランザクションをコミット (またはロールバック) してロックを解除するまでブロックします。その後、Oracle は行が B の基準を満たしているかどうかを判断し、例では行を B に返しません。これは機能しますが、トランザクションの制御方法によっては、マルチスレッド プロセスが事実上シングルスレッドになる可能性があることを意味します働く必要があります。

スケーラビリティを改善するために考えられる方法

  • これを解決するためのコンシューマーでの比較的一般的なアプローチは、テーブルからデータを読み取り、作業を別のスレッドに分割し、テーブルを適切に更新する単一のコーディネーター スレッドを用意することです (スレッドがジョブを再割り当てする方法を知ることを含む)。割り当てられていたものは死亡しました)。
  • Oracle 11.1以降を使用している場合は、各セッションが基準を満たし、ロックされていない最初の行を取得するようにSKIP LOCKEDFOR UPDATEを使用できます(句は以前のバージョンに存在しましたが、文書化されていないため、正しく機能しない可能性があります) )。
  • のテーブルを使用する代わりに、ImportJobs複数のコンシューマを持つキューを使用できます。これにより、追加のロックを構築する必要なく、Oracle が各プロセスにメッセージを配布できるようになります (Oracle キューはすべてバックグラウンドで実行しています)。
于 2013-02-27T16:01:37.833 に答える
2

データベースの組み込みロック メカニズムを使用する必要があります。特に RDBMS は同時実行性と一貫性を処理するように設計されているため、車輪を再発明しないでください。

Oracle 11g では、このSKIP LOCKED機能を使用することをお勧めします。たとえば、各プロセスは次のような関数を呼び出すことができます (id数値であると仮定します):

CREATE OR REPLACE TYPE tab_number IS TABLE OF NUMBER;

CREATE OR REPLACE FUNCTION reserve_jobs RETURN tab_number IS
   CURSOR c IS 
      SELECT id FROM IMPORTJOBS WHERE STATUS = 'REGISTERED'
      FOR UPDATE SKIP LOCKED;
   l_result tab_number := tab_number();
   l_id number;
BEGIN
   OPEN c;
   FOR i IN 1..10 LOOP
      FETCH c INTO l_id;
      EXIT WHEN c%NOTFOUND;
      l_result.extend;
      l_result(l_result.size) := l_id;
   END LOOP;
   CLOSE c;
   RETURN l_result;
END;

これにより、ロックされていない 10 行 (可能な場合) が返されます。これらの行はロックされ、セッションは互いにブロックしません。

10g 以前では、Oracle は一貫した結果を返すため、FOR UPDATE賢明に使用すれば、説明した問題は発生しないはずです。たとえば、次のことを検討してSELECTください。

SELECT *
  FROM IMPORTJOBS 
 WHERE STATUS = 'REGISTERED'
   AND rownum <= 10
FOR UPDATE;

すべてのプロセスがこの SELECT で行を予約するとどうなるでしょうか? それはあなたのシナリオにどのように影響しますか:

  1. セッション A は、処理されていない 10 行を取得します。
  2. セッション B は同じ 10 行を取得し、ブロックされてセッション A を待ちます。
  3. セッション A は、選択された行のステータスを更新し、そのトランザクションをコミットします。
  4. データが変更され、指定されているため、Oracle はセッション B の select を最初から (自動的に) 再実行しますFOR UPDATE(この句により、Oracle はブロックの最新バージョンを取得するように強制されます)。
    これは、セッション B が 10 個の新しい行を取得することを意味します。

したがって、このシナリオでは、一貫性の問題はありません。また、行を要求してそのステータスを変更するトランザクションが高速であると仮定すると、同時実行性の影響は軽微です。

于 2013-02-27T16:10:38.870 に答える
1

バージョニングとオプティミスティック コンカレンシーを使用します。

テーブルには、モデルで=IMPORTJOBSとしてマークするタイムスタンプ列が必要です。EF が更新を実行しようとすると、タイムスタンプ列が更新ステートメントに組み込まれます: .ConcurrencyModeFixedWHERE timestamp = xxxxx

の場合B、その間にタイムスタンプが変更されたため、同時実行例外が発生します。この場合、更新をスキップして処理します。

私はSQLサーバーのバックグラウンドから来ており、タイムスタンプ(または行バージョン)に相当するOracleを知りませんが、レコードが更新されたときに自動更新されるフィールドであるという考えです。

于 2013-02-27T16:02:19.370 に答える