web-dev-qa-db-ja.com

Oracleの更新動作の選択

解決しようとしている問題は次のようになります。

  • カードを表す行でいっぱいのテーブルがあります。予約取引の目的は、クライアントにカードを割り当てることです
  • カードは多くのクライアントに属することはできません
  • しばらくして(購入しない場合)、カードを利用可能なリザルトのプールに戻す必要があります
  • 多くのクライアントが同時に予約することができます
  • データの保存にはOracleデータベースを使用するため、ソリューションは少なくともOracle 11で機能する必要があります

私たちの解決策は、カードにステータスを割り当て、その予約日を保存することです。カードを予約するときは、「select for update」ステートメントを使用して行います。クエリは、利用可能なカードと、かなり前に予約されたカードを探します。

ただし、クエリは期待どおりに機能しません。

問題を説明するために簡単な状況を用意しました。データでいっぱいのcard_numbersテーブルがあります-すべての行にnull以外のID番号があります。それでは、それらのいくつかをロックしてみましょう。

-- first, in session 1
set autocommit off;

select id from card_numbers  
where id is not null  
and rownum <= 1  
for update skip locked;

ここではトランザクションをコミットしません。行をロックする必要があります。

-- later, in session 2
set autocommit off;

select id from card_numbers  
where id is not null  
and rownum <= 1  
for update skip locked;

予想される動作は、両方のセッションで、クエリ条件を満たす単一の異なる行を取得することです。

ただし、この方法では機能しません。クエリの「スキップロック」部分を使用するかどうかに応じて、動作が変わります。

  • 「ロックされたスキップ」なし-2番目のセッションがブロックされます-セッション1でトランザクションのコミットまたはロールバックを待機しています
  • 「スキップロック」-2番目のクエリはすぐに空の結果セットを返します

それで、この長い紹介の後に問題が来ます。

Oracleでこのような望ましいロック動作は可能ですか?はいの場合、何が悪いのでしょうか?正しい解決策は何でしょうか?

18
mateusz.fiolka

FOR UPDATE SKIP LOCKEDで発生した動作は このブログのメモ で説明されています。私の理解では、FOR UPDATE句はWHERE句の後に評価されます。 SKIP LOCKEDは、返されるはずの行のうち、ロックされている行がないことを保証する追加のフィルターのようなものです。

ステートメントは論理的には次のようになります。card_numbersから最初の行を見つけて、ロックされていない場合はそれを返します。明らかにこれはあなたが望むものではありません。

以下は、記述した動作を再現する小さなテストケースです。

SQL> CREATE TABLE t (ID PRIMARY KEY)
  2  AS SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 1000;

Table created

SESSION1> select id from t where rownum <= 1 for update skip locked;

        ID
----------
         1

SESSION2> select id from t where rownum <= 1 for update skip locked;

        ID
----------

2番目の選択から行は返されません。カーソルを使用して、この問題を回避できます。

SQL> CREATE FUNCTION get_and_lock RETURN NUMBER IS
  2     CURSOR c IS SELECT ID FROM t FOR UPDATE SKIP LOCKED;
  3     l_id NUMBER;
  4  BEGIN
  5     OPEN c;
  6     FETCH c INTO l_id;
  7     CLOSE c;
  8     RETURN l_id;
  9  END;
 10  /

Function created

SESSION1> variable x number;
SESSION1> exec :x := get_and_lock;

PL/SQL procedure successfully completed
x
---------
1

SESSION2> variable x number;
SESSION2> exec :x := get_and_lock;

PL/SQL procedure successfully completed
x
---------
2

明示的にカーソルをフェッチしたので、1行だけが返されます(ロックされるのは1行だけです)。

17
Vincent Malgrat

他の回答はすでにさまざまなSELECT .. FOR UPDATEバリアント、オラクルはFOR UPDATE SKIP LOCKEDを直接使用し、Oracle AQ代わりに:

http://download.Oracle.com/docs/cd/B28359_01/server.111/b28286/statements_10002.htm#i2066346

を使用しております Oracle AQ私たちのアプリケーションでは、やや急な学習曲線の後、データベースで直接プロデューサー/コンシューマーを処理するのは非常に便利な方法であることが確認できます

6
Lukas Eder

ヴィンセントの答えが間違っているわけではありませんが、私はそれを異なって設計したでしょう。

私の最初の本能は、利用可能な最初のレコードを更新するために選択し、「reserved_date」でレコードを更新することです。 XXX時間が経過してもトランザクションが完了していない場合は、レコードのreserved_dateをnullに更新して、レコードを再度解放します。

私は物事をできるだけシンプルに保つようにしています。私にとって、これはもっと簡単です。

3
user734922