我有一个 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 语句也不是原子的,可能会遇到类似的问题。
有谁知道如何防止这种情况发生?