如何防止在 Oracle SQL 中可序列化的隔离级别下出现不一致的数据?

tr3*_*sta 5 oracle

我有下表:

InStores
+--------+---------+-----+
| ProdID | StoreID | qty |
+--------+---------+-----+
| p1     | s1      |  25 |
| p1     | s2      |  70 |
+--------+---------+-----+
Run Code Online (Sandbox Code Playgroud)

和两个事务,T1 和 T2。

首先是T1:

declare s number(10);

begin
 SELECT SUM(qty) into s
 FROM instores
 WHERE prodID = 'p1';

 if s > 90 then
    update instores
    set qty = qty - 30
    where prodid = 'p1'
    and storeid = 's2';
 end if;
end;
Run Code Online (Sandbox Code Playgroud)

T1 看到 95 的总和并从 p1、s2 中减去 30(但尚未提交)。

T2 完全一样:

declare s number(10);

begin
 SELECT SUM(qty) into s
 FROM instores
 WHERE prodID = 'p1';

 if s > 90 then
    update instores
    set qty = qty - 30
    where prodid = 'p1'
    and storeid = 's2';
 end if;
end;
Run Code Online (Sandbox Code Playgroud)

它还看到 95 的总和,因为 T1 还没有提交,也没有更新,因为有一个来自 T1 的写锁。

现在 T1 提交,T2 现在能够继续并从 p1、s2 中减去 30 的值。

我们最终得到 p1、s2 的值 10,而不是预期值 40,因为 T2 应该看到 65 的总和(因为 T1 的第一次更新(减法))并且不应该进行第二次减法。

我知道 Oracle SQL 与快照隔离一起使用,它强制所有事务使用最新提交的值,但是如何解决这个问题?我想解决方案是,当 T1 处于活动状态时,它应该阻止所有其他事务开始,但我不完全知道如何实现这一点。我能做什么?

And*_*lfe 1

你的期望并不完全正确。

当 T2 发出 UPDATE 时,它的局部变量 's' 中的总和为 95。也就是说,T2 查询已完成,并且 T2 'if' 语句在 T1 提交之前发生,并且由于总和大于 90,因此它继续进行更新。

没有理由更新局部变量“s”来跟踪 T1 提交。

我认为您应该尝试在循环中使用 SELECT FOR UPDATE NOWAIT,例如

<<do_tr3quart1sta_thing>> declare
   totqty number;
   lock_nowait_exception exception;
   pragma exception_init (lock_nowait_exception, -54);
begin

   <<try_for_lock>> loop
       begin
           select sum (qty) into totqty
           from instores where prodid = 'p1'
           FOR UPDATE OF qty;
           exit try_for_lock if sql%rowcount > 0;
       exception
           when lock_nowait_exception then
               exit try_for_lock;
       end;
       dbms_lock.sleep (2); -- for heaven's sake please don't run these in a tight spin!
   end loop try_for_lock;

   if totqty > 90
   then
       update instores
       set qty = qty - 30
       where prodid = 'p1'
         and storeid = 's2';
   end if;
   commit; -- release the lock!
end do_tr3quart1sta_thing;
/
Run Code Online (Sandbox Code Playgroud)

没有实验时间 - 我认为如果NOWAIT 被锁定,它会引发异常 -54,但在我通常的皮带和吊带方式中,我允许 SELECT FOR UPDATE 仅返回零行的可能性。

另请注意,这将使任何其他引发的异常冒泡并爆炸。

在这种情况下你到底如何才能让T2阻止呢?你可以每秒运行这个东西数十万次!