[英]Insert or Update using Oracle and PL/SQL
我有一个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告诉您的那样,您遇到的情况并非不可能。 如果另一个进程插入了您要插入但尚未提交的密钥,则可以得到描述的行为。 更新不会看到插入的记录,但是即使插入的行尚未提交,也禁止尝试将重复值添加到唯一索引中。
想到的唯一解决方案是最小化任何未提交的插入在该表上徘徊的时间,或者实施某种锁定方案,或者在插入失败时等待其他事务完成。
不太同意DCookie。
如果会话A插入值“ blue”(强制为唯一),然后会话B插入值“ blue”,则会话B将等待会话A的锁。如果会话A提交,则会话B将获得约束违反。 如果会话A进行回滚,则将允许会话B继续。
会话A可能会有很小的范围来插入行并提交,会话B会得到约束违例,然后在会话B更新之前删除该行。 我认为那不太可能。
我首先来看一下target_total表上是否只有一个唯一约束。 如果不是,则要非常确定是哪个约束导致了违规。 还要检查唯一索引和约束。
检查是否存在任何数据类型不匹配或干扰触发。 在选择匹配中,NUMBER(2,0)可能不等于1.1数值,但在插入时1.1会被截断为1.0,从而可能会引发约束冲突。 在我的示例中,如果触发器强制使用大写字母“ BLUE”,则选择可能无法在“ blue”上匹配,插入可能会在“ BLUE”上的重复键上失败,并且随后的插入也会在“ blue”上匹配失败蓝色”。
然后检查变量命名。 在INSERT .... VALUES( 标识符 )中, 标识符必须是PL / SQL变量。 但是,SELECT * FROM table WHERE column = identifier ,则标识符可能是列名,而不是PL / SQL变量。 如果存在列名或accountId函数,则该列名将优先于同名的PL / SQL变量。 给PL / SQL变量加上前缀是一个好习惯,以确保永远不会出现这样的名称空间冲突。
我唯一的另一个想法是,由于您正在运行多线程,因此线程是否有可能发生冲突。 在实时环境中,线程可能会碰到其他会话的锁时,这种可能性更大。 这可能会迫使他们以一种奇怪的方式进行同步,而这种方式在测试中没有出现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.