我们试图解决的问题是这样的。
我们的解决方案是为卡指定一个状态,并存储它的预订日期。在预订卡时,我们使用“select for update”语句来完成。该查询查找可用的卡和很久以前保留的卡。
但是我们的查询无法正常工作。
我准备了一个简化的情况来解释这个问题。 我们有一个card_numbers表,充满了数据 - 所有行都有非空的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中可能存在所需的锁定行为吗?如果是,那么我们做错了什么?什么是正确的解决方案?
答案 0 :(得分:17)
this blog note中描述了FOR UPDATE SKIP LOCKED遇到的行为。我的理解是在WHERE子句之后评估FOR UPDATE子句。 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
----------
第二个选择不返回任何行。您可以使用游标解决此问题:
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 :(得分:6)
虽然其他答案已经充分解释了数据库中各种SELECT .. FOR UPDATE
变体的情况,但我认为值得一提的是,Oracle不鼓励直接使用FOR UPDATE SKIP LOCKED
并鼓励使用Oracle AQ
代替:
http://download.oracle.com/docs/cd/B28359_01/server.111/b28286/statements_10002.htm#i2066346
我们在我们的应用程序中使用Oracle AQ
我可以确认,在经过一段时间的学习曲线之后,它可以是一种直接在数据库中处理生产者/消费者的非常方便的方法
答案 2 :(得分:3)
并不是说文森特的答案是错的,但我会以不同的方式设计它。
我的第一直觉是选择更新第一个可用记录并使用“reserved_date”更新记录。在XXX时间过去并且事务未完成之后,将记录的reserved_date更新回null以再次释放记录。
我尽量让事情变得简单。对我来说,这更简单。