6

假设我有下表具有以下约束:

create table test as (
    select 1 as id, 'a' as name from dual 
    union all 
    select 2, 'b' from dual 
    union all 
    select 3, 'c' from dual
);

create unique index ind on test(name);

alter table test add constraint constr unique (name);

select * from test;

        ID NAME
---------- ----
         1 a   
         2 b   
         3 c   

现在假设我执行以下操作MERGE

merge into test t using (
    select 4 as id, 'b' as name from dual 
    union all 
    select 2 as id, null as name from dual 
) s on (s.id = t.id) 
    when matched then update set t.name = s.name
    when not matched then insert(t.id, t.name) values(s.id, s.name)

select * from test;

        ID NAME
---------- ----
         1 a   
         2     
         3 c   
         4 b   

以上MERGE 失败吗?如果UPDATE先s,后INSERTs,则索引/约束在执行过程中不会失效。但是如果先INSERTs,再UPDATEs,索引会暂时失效,语句可能会失败?

有人可以详细解释(或指出正确的方向)Oracle RDBMS 如何处理此类问题吗?此外,使用该LOG ERRORS INTO子句时的处理方式是否相同?

我问这个问题以及为什么我需要一个解决方案的主要原因:我的 MERGE 语句运行了几个小时,并带有 LOG ERRORS INTO 子句。错误日志似乎作为一个自主事务工作。在语句完成更新插入之前很久就记录了一些唯一约束错误(基于唯一索引)(其中,我看到序列上升),我不知道为什么(尽管最后,在更新插入之后,应该没有唯一约束无效)。当我查看 ERROR 表时,我看到 ORA-00001:INSERT 操作违反了唯一约束 (XXX.YYY)。我可以将此记录从 ERROR 表插入到主表中,而不会导致唯一约束失败。所以我想知道为什么首先记录错误。

编辑:下面的答案断言,当执行语句时,约束在语句末尾强制执行。我理解并同意(同时我想了解更多关于此类情况下的索引维护的详细信息)。我不明白为什么这个问题仍然没有回答是为什么我有这些 ORA-00001: unique constraint (XXX.YYY) 违反了错误记录,而它们不应该记录。似乎错误记录机制不是以原子方式运行的。

编辑2:

Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
PL/SQL Release 11.2.0.4.0 - Production
CORE    11.2.0.4.0  Production
TNS for Solaris: Version 11.2.0.4.0 - Production
NLSRTL Version 11.2.0.4.0 - Production

EDIT3: 我玩了一下,能够重现这个错误:

drop table test;

drop table err_test;

create table test as (
    select 1 as id, 'a' as name from dual 
    union all 
    select 2, 'b' from dual 
    union all 
    select 3, 'c' from dual
);

create unique index ind on test(name);

alter table test add constraint constr unique (name);

--select test.rowid, test.* from test;

BEGIN
DBMS_ERRLOG.CREATE_ERROR_LOG (
   dml_table_name            => 'TEST',
   err_log_table_name        => 'ERR_TEST');
END;
/

--truncate table err_test;

select * from err_test;

merge /*+ PARALLEL(t 2) */ into test t using (
    select 4 as id, 'b' as name from dual 
    union all 
    select 2 as id, null as name from dual 
) s on (s.id = t.id) 
    when matched then update set t.name = s.name
    when not matched then insert(t.id, t.name) values(s.id, s.name)
LOG ERRORS INTO ERR_TEST('TEST,ID:'||s.id) REJECT LIMIT UNLIMITED;

select * from err_test;

在最后select * from err_test;我总是得到:ORA-00001: unique constraint (XXX.CONSTR) violated。现在奇怪的是,真正的 MERGE 语句(在生产中)不再在 PARALLEL 中工作,有时我仍然会收到这个错误......

EDIT4: 我标记为已接受的最佳答案,尽管问题本身并没有完全回答。看来这只是Oracle中的一个错误。

4

2 回答 2

5

这种合并永远不会失败。

这在此处通过示例进行解释:数据库概念 - 5. 数据完整性

对于不可延迟的约束(默认):

在不可延迟约束中,Oracle 数据库从不将约束的有效性检查推迟到事务结束。相反,数据库在每个语句的末尾检查约束。如果违反了约束,则语句回滚。



上面的意思是,在整个单个 SQL 语句的末尾 检查约束,而不是在它们执行期间。



下面,在本文档中,您可以找到两个交易示例,它们在“内部”执行期间违反了一些约束规则,但最终它们满足了所有约束,并且是合法的,因为:

...因为数据库在语句完成后有效地检查约束。图 5-4 显示了数据库在检查约束之前执行了整个 SQL 语句的操作。

最后他们还写道:

本节中的示例说明了 INSERT 和 UPDATE 语句期间的约束检查机制,但数据库对所有类型的 DML 语句使用相同的机制。相同的机制用于所有类型的约束,而不仅仅是自引用约束。

于 2014-12-10T18:19:58.193 回答
4

正如其他用户指出的那样,作业的“LOG ERRORS INTO”部分发生在语句执行之后(更新和插入部分),同时检查约束。因此,您可以在约束检查完成之前插入错误。这就是为什么您会在语句完全完成之前看到插入的错误。

作为这个观察的答案:

我可以将此记录从 ERROR 表插入到主表中,而不会导致唯一约束失败。所以我想知道为什么首先记录错误。

确保您在一个 Merge 语句中包含全部信息。如果您不更新同一语句中的值,而是在插入失败和重试之间发生的另一个语句中更新值,那么事情是可以解释的。

(我的意思是 USING 部分的记录不在同一个语句中。

  • 会话1:合并使用 select 4 as id, 'b' as name from dual (错误插入日志)
  • 会话 2:使用 select 2 as id, null as name from dual 提交确定合并
  • 会话 3:您重试插入,它可以工作

)

如果您可以用一条语句重现错误,那将是一个问题。但是您的环境中有许多会话。请检查您的合并语句的来源。您可能会迟到,或类似情况。

于 2015-03-20T11:49:33.000 回答