Oracle选择更新行为

时间:2011-05-01 07:57:02

标签: sql oracle locking blocking

我们试图解决的问题是这样的。

  • 我们有一个表格,代表卡片。预订交易的目的是将卡分配给客户
  • 一张卡不能属于许多客户
  • 一段时间后(如果没有购买),必须将卡片退回到可用的resurces池中
  • 许多客户可以同时进行预订
  • 我们使用Oracle数据库存储数据,因此解决方案必须至少在Oracle 11上运行

我们的解决方案是为卡指定一个状态,并存储它的预订日期。在预订卡时,我们使用“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;

预期的行为是,在两个会话中,我们得到一个满足查询条件的不同行。

然而它不起作用。取决于我们是否使用查询的“跳过锁定”部分 - 行为更改:

  • 没有“跳过锁定” - 第二个会话被阻止 - 等待第一个会话中的事务提交或回滚
  • with“skip locked” - 第二个查询立即返回空结果集

因此,经过这么长时间的介绍就出现了问题。

Oracle中可能存在所需的锁定行为吗?如果是,那么我们做错了什么?什么是正确的解决方案?

3 个答案:

答案 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以再次释放记录。

我尽量让事情变得简单。对我来说,这更简单。