2011-03-08 41 views
4

我有一个PL/SQL函数在Oracle数据库上执行更新/插入操作,该数据库维持目标总数并返回现有值和新值之间的差异。
这里是我到目前为止的代码:使用Oracle和PL/SQL插入或更新

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语句也不是原子的,可能会遇到类似的问题。

有没有人有任何想法如何防止这种情况发生?

+0

是否只有一列(accountID)上的唯一索引?还是有没有第二列,你没有显示,以简化说明? – redcayuga 2011-03-09 16:39:15

+0

如何定义唯一约束?一个独特的索引?具有唯一显式约束的非唯一索引?如果定义了明确的唯一约束,它是否可延迟? – redcayuga 2011-03-09 16:47:28

回答

1

正如甲骨文告诉你的,这不是一个不可能的情况。如果另一个进程插入了您尝试插入但尚未提交的密钥,则可以获取所描述的行为。更新不会看到插入的记录,但即使插入的行尚未提交,也会禁止尝试将重复值添加到唯一索引。

想到的唯一解决方案是尽量减少任何未提交的插入对此表执行的时间量,或者实现某种锁定方案,或者等待插入操作失败以等待其他事务完成。

1

不太同意DCookie。如果会话A插入值“蓝色”(强制为唯一),然后会话B插入值“蓝色”,则会话B将等待来自会话A的锁定。如果会话A提交,则会话B将会违反约束。如果会话A执行回滚,则会话B将被允许继续。

可能会有一个非常小的范围让会话A插入一行并提交它,会话B得到约束违例,然后在会话B更新之前删除该行。尽管如此,我很难判断。

我首先看看target_total表中是否只有一个唯一约束。如果不是,你想要确定哪些约束导致违规。还检查唯一索引以及约束。

检查是否有任何数据类型不匹配或干扰触发。 NUMBER(2,0)在选择匹配中可能不等于1.1数字值,但在插入时,1.1将被截断为1.0,可能会触发违反约束条件。在我的示例中,如果触发器强制使用大写“BLUE”,则select可能无法在“blue”上匹配,插入可能会在“BLUE”上的重复键上失败,并且后续插入也无法匹配“蓝色”。

然后检查变量命名。在INSERT .... VALUES(标识符)中,那么标识符必须是PL/SQL变量。然而,SELECT * FROM表WHERE列= 标识符,然后标识符可能是列名称而不是PL/SQL变量。如果列名或accountId的功能优先于同名的PL/SQL变量。为PL/SQL变量加上前缀以确保永远不会存在这样的名称空间冲突是一种好习惯。

我唯一的想法是,由于您正在运行多线程,线程是否有任何潜在的冲突。当线程可能从其他会话锁定时,这可能更有可能在实时环境中。这可能会迫使他们以一种不会在测试中出现的奇怪方式进行同步。