解決しようとしている問題は次のようになります。
私たちの解決策は、カードにステータスを割り当て、その予約日を保存することです。カードを予約するときは、「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;
予想される動作は、両方のセッションで、クエリ条件を満たす単一の異なる行を取得することです。
ただし、この方法では機能しません。クエリの「スキップロック」部分を使用するかどうかに応じて、動作が変わります。
それで、この長い紹介の後に問題が来ます。
Oracleでこのような望ましいロック動作は可能ですか?はいの場合、何が悪いのでしょうか?正しい解決策は何でしょうか?
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行だけです)。
他の回答はすでにさまざまな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
私たちのアプリケーションでは、やや急な学習曲線の後、データベースで直接プロデューサー/コンシューマーを処理するのは非常に便利な方法であることが確認できます
ヴィンセントの答えが間違っているわけではありませんが、私はそれを異なって設計したでしょう。
私の最初の本能は、利用可能な最初のレコードを更新するために選択し、「reserved_date」でレコードを更新することです。 XXX時間が経過してもトランザクションが完了していない場合は、レコードのreserved_dateをnullに更新して、レコードを再度解放します。
私は物事をできるだけシンプルに保つようにしています。私にとって、これはもっと簡単です。