sql - 使用 Oracle 和 PL/SQL 插入或更新

标签 sql oracle plsql upsert ora-00001

我有一个 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/

相关文章:

MySQL:如果值仅为数字则更新列

mysql - JOIN SQL 函数结果与一张表中的多个 WHERE

sql - 如果查询返回一行,如何选择一条记录,如果查询返回更多行,如何选择不记录?

sql - 如何处理 Oracle 中 NOT IN 和 NOT LIKE 语句中的空值?

MySQL 标准化与性能问题

java - 您将 SQL 查询放在哪里?

oracle - 清理 Oracle 序列

Oracle 减查询。如果顶部 SQL 和底部 SQL 不包含 NULL,如何获得带有 NULL 的结果?

sql - Oracle:检查 dml 语句

sql - 包大小对 Oracle 10g 性能的影响