我们试图解决的问题看起来像这样.
>我们有一张充满行代表桌子的桌子.预约交易的目的是将卡分配给客户端
>卡不能属于许多客户端
>经过一段时间(如果没有购买),必须将卡片退回到可用的复制池中
>许多客户可以同时进行预订
>我们使用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;
预期的行为是在两个会话中,我们得到一个满足查询条件的单独的不同行.
但是它不会这样工作.根据我们是否使用查询的“跳过锁定”部分,而不是行为变化:
>没有“跳过锁定” – 第二个会话被阻止 – 等待会话中的事务提交或回滚
>用“skip locked” – 第二个查询立即返回空结果集
所以,在这个漫长的介绍之后出现的问题.
在Oracle中可以使用所需的锁定方式吗?如果是,那我们做错了什么?什么是正确的解决方案?
解决方法
对于FOR UPDATE SKIP LOCKED遇到的行为已在
this blog note中进行了描述.我的理解是,在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
由于我明确地获取了游标,所以只会返回一行(只有一行被锁定).