我有一个 PL/SQL 函数,它在维护目标总数的 Oracle 数据库上执行更新/插入并返回现有值和新值之间的差异。
这是我到目前为止的代码:
FUNCTION calcTargetTotal(accountId varchar2, newTotal numeric ) RETURN number is
oldTotal numeric(20,6);
difference numeric(20,6);
begin
difference := 0;
begin
select value into oldTotal
from target_total
WHERE account_id = accountId
for update of value;
if (oldTotal != newTotal) then
update target_total
set value = newTotal
WHERE account_id = accountId
difference := newTotal - oldTotal;
end if;
exception
when NO_DATA_FOUND then
begin
difference := newTotal;
insert into target_total
( account_id, value )
values
( accountId, newTotal );
-- sometimes a race condition occurs and this stmt fails
-- in those cases try to update again
exception
when DUP_VAL_ON_INDEX then
begin
difference := 0;
select value into oldTotal
from target_total
WHERE account_id = accountId
for update of value;
if (oldTotal != newTotal) then
update target_total
set value = newTotal
WHERE account_id = accountId
difference := newTotal - oldTotal;
end if;
end;
end;
end;
return difference
end calcTargetTotal;
这在具有多个线程的单元测试中按预期工作,永远不会失败。
但是,当加载到实时系统上时,我们看到此操作失败,堆栈跟踪如下所示:
ORA-01403: no data found
ORA-00001: unique constraint () violated
ORA-01403: no data found
行号(我已将其删除,因为它们在上下文中毫无意义)验证第一次更新由于没有数据而失败,由于唯一性导致插入失败,以及第二次更新因没有数据而失败,这应该是不可能的。
从我在其他线程上阅读的内容来看,MERGE 语句也不是原子的,可能会遇到类似的问题。
有没有人有任何想法如何防止这种情况发生?
最佳答案
正如 Oracle 告诉您的那样,这不是您遇到的不可能的情况。如果另一个进程插入了您尝试插入但尚未提交的 key ,您可以获得所描述的行为。更新不会看到插入的记录,但即使插入的行尚未提交,也禁止尝试将重复值添加到唯一索引。
想到的唯一解决方案是最大限度地减少任何未提交的插入为此表挂起的时间,或者实现某种锁定方案,或者在插入失败时等待其他事务完成。
关于sql - 使用 Oracle 和 PL/SQL 插入或更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5236666/