【ORACLE】记录一些ORACLE的merge into语句的BUG
一、自相矛盾-DML重启动行为差异,违反acid原则
发现版本:10g ~ 23ai
这个用例在我之前的文章里有提过,ORACLE和PG系关于并发事务行为有一个非常大的差异,就是ORACLE在某些并发冲突的场景下会进行DML重启动,但是对比下面两个例子,可以发现无论采取何种事务一致性实现逻辑,在read commit下,最终的结果无外乎得到(1,‘Alice’)、(10,‘Alice’)、(1,‘Tom’)三种其一,但ORACLE的merge into 却得到了(10,‘Tom’)这一理论不应该存在的记录。
用例一:
drop table test_dml_restart;create table test_dml_restart(id number ,name varchar2(10));
insert into test_dml_restart values (1,'Alice');
commit;--会话一,执行后不提交
update test_dml_restart set id=10 where name='Alice';--会话二执行,被锁
update test_dml_restart set name='Tom' where id=1;--会话一提交,会话二自动解锁,更新0行,会话2根据id=1进行了重启动,找不到id=1的记录
commit;--会话二查询
select * from test_dml_restart;10,Alice
用例二:
drop table test_dml_restart;create table test_dml_restart(id number ,name varchar2(10));
insert into test_dml_restart values (1,'Alice');
commit;--会话一,执行后不提交
update test_dml_restart set id=10 where name='Alice';--会话二执行,被锁
merge into test_dml_restart t1
using (select * from dual) t2
on (t1.id=1)
when matched then
update set t1.name='Tom';--会话一提交,会话二自动解锁,更新1行,会话2仍然按旧的id找到了数据进行更新
commit;--会话二查询
select * from test_dml_restart;10,Tom
在asktom上也有人发现过,但从10g到目前最新的23ai,这个问题一直未修复
https://asktom.oracle.com/ords/f?p=100:11:::NO::P11_QUESTION_ID:61865893444475
merge and write consistency
Andrey N. Edemsky, May 04, 2006 - 3:51 pm UTC
其实这个问题在ORACLE里通过改写SQL规避,就是把on里的条件,复制一份到update后面的where里
merge into test_dml_restart t1
using (select * from dual) t2
on (t1.id=1)
when matched then
update set t1.name='Tom' where t1.id=1;
二、百密一疏-触发ORA600的外关联
发现版本:11g 12c 18c 19c
create table test1 (col1 number,col2 number);create table test2 (col1 number,col2 number);insert into test1 values (1,1);merge into test2
using test1 on (test1.col1=test2.col1(+))
when matched thenupdate set test2.col2=test1.col2;ORA-00600: 内部错误代码, 参数: [25027], [3], [3], [0], [0], [0], [1], [1], [], [], [], []
虽然这个sql本身的确不对,但是ORACLE报错却是ORA-600这个ORACLE自己没有估计到的报错。
19.20及最新的23ai版本已修复这个问题,在23ai中该SQL执行结果是merge 0行。
三、自欺欺人-不能更新on中引用的列
版本:9i ~ 23ai
详见这篇 【ORACLE】你以为的真的是你以为的么?–ORA-38104: Columns referenced in the ON Clause cannot be updated
这个是个ORACLE故意做的限制,本身可以说不算BUG,但是通过特殊方式能绕过这个限制,就可以算个BUG了。所以ORACLE要么承认这个设计是BUG,要么承认开发遗漏了场景也还是BUG,总归有一边是BUG。
merge into test_merge_a a
using test_merge_b b
on (a.id=b.id)
when matched thenupdate set id=2;ORA-38104: 无法更新 ON 子句中引用的列: "A"."ID"--改写
merge into test_merge_a a
using test_merge_b b
on ((a.id=b.id or 1=2) and (a.id=b.id or 1=2 ))
when matched thenupdate set a.id=2;Plan Hash Value : 4101543598 --------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 1 | 13 | 4 | 00:00:01 |
| 1 | MERGE | TEST_MERGE_A | | | | |
| 2 | VIEW | | | | | |
| * 3 | HASH JOIN | | 1 | 76 | 4 | 00:00:01 |
| 4 | TABLE ACCESS FULL | TEST_MERGE_B | 1 | 32 | 2 | 00:00:01 |
| 5 | TABLE ACCESS FULL | TEST_MERGE_A | 1 | 44 | 2 | 00:00:01 |
--------------------------------------------------------------------------------Predicate Information (identified by operation id):
------------------------------------------
* 3 - access("A"."ID"="B"."ID")
以上是较新版本中需要注意一些问题。
在早期版本中已经修复过的就不全部列举了,MOS上搜merge into的BUG大把大把的,翻了N页没见底,什么更新视图、分区表、并行、并发、行归档、dblink、压缩表等等有一堆功能和merge into组合使用有问题的,这里只写几个简单的merge into语句有问题的场景
四、无中生有-源数据为空的结果集也能匹配上
版本:11.2.0.2.0
https://asktom.oracle.com/ords/f?p=100:11:0::::P11_QUESTION_ID:9537112000346374938
https://asktom.oracle.com/ords/f?p=100:11:::NO::P11_QUESTION_ID:61865893444475
Same guy - Correction
GPU, August 26, 2013 - 5:11 pm UTC
create table test_merge_null(col1 varchar2(20) not null);MERGE INTO test_merge_null
USING (SELECT dummy
FROM DUAL
WHERE 1 = 2) s
ON (1 = 2)
WHEN NOT MATCHED
THEN
INSERT (col1)
VALUES (s.dummy);
这是11.2.0.2.0 中的 bug,已在 11.2.0.3.0 中修复。还好,11.2.0.2.0这个版本用得少。
五、一山能容二虎-突破主键唯一限制
发现版本:10.2.0.1.0
通过direct path write,能突破主键限制进行插入,使表中数据违反有效的唯一约束,案例来自:
https://blog.csdn.net/jackpk/article/details/3788143
create table KL_TEST
(a NUMBER(15) not null,b NUMBER(15) not null,c NUMBER(15) not null,d NUMBER(15) not null,e NUMBER(15) not null,f NUMBER(15) not null
);10 ;Table created.alter table KL_TESTadd constraint KL_TEST_PK primary key (A, B, C, D)3 ;Table altered.CREATE TABLE KL_TEST_1 NOLOGGING PCTFREE 0 ASSELECT * FROM KL_TEST3 WHERE 1=2;Table created.SYS@ora10g> INSERT INTO KL_TEST_1 VALUES (1,1,1,1,1,1);1 row created.SYS@ora10g> INSERT INTO KL_TEST_1 VALUES (1,2,1,2,1,2);1 row created.SYS@ora10g> INSERT INTO KL_TEST_1 VALUES (1,1,1,1,1,1);1 row created.SYS@ora10g> commit;Commit complete.MERGE /*+ APPEND */ INTO KL_TEST trgt
USING
(select fct.a,fct.b,fct.c,fct.d,fct.e,fct.f from KL_TEST_1 fct) tmp
ON
(tmp.a = trgt.aand tmp.b = trgt.band tmp.c = trgt.cand tmp.d = trgt.dand tmp.e = trgt.eand tmp.f = trgt.f)WHEN NOT MATCHED THENINSERT (a,b,c,d,e,f)13 VALUES (tmp.a,tmp.b,tmp.c,tmp.d,tmp.e,tmp.f);3 rows merged.SYS@ora10g> commit;Commit complete.SYS@ora10g> SELECT COUNT(*) FROM KL_TEST;COUNT(*)
----------0SYS@ora10g> SELECT /*+ FULL (KL_TEST)*/ COUNT(*) FROM KL_TEST;COUNT(*)
----------3SYS@ora10g> select * from kl_test;A B C D E F
---------- ---------- ---------- ---------- ---------- ----------1 1 1 1 1 11 1 1 1 1 11 2 1 2 1 2SYS@ora10g> select CONSTRAINT_NAME,CONSTRAINT_TYPE, STATUS from dba_constraints where table_name ='KL_TEST';CONSTRAINT_NAME C STATUS
------------------------------ - --------
SYS_C005398 C ENABLED
SYS_C005397 C ENABLED
SYS_C005396 C ENABLED
SYS_C005395 C ENABLED
SYS_C005394 C ENABLED
SYS_C005393 C ENABLED
KL_TEST_PK P ENABLED7 rows selected.SYS@ora10g> insert into kl_test values (1,1,1,1,1,1);1 row created.SYS@ora10g> select * from kl_test;A B C D E F
---------- ---------- ---------- ---------- ---------- ----------1 1 1 1 1 11 1 1 1 1 11 2 1 2 1 21 1 1 1 1 1
在10.2.0.5.0和11.2.0.4版本中已经没这个问题了
总结
ORACLE从很早的版本就增加了merge into这一语法,虽然给开发人员带来了很大的便利性,但是其BUG数之多非常恐怖,很多BUG从发现到修复跨越了数十年的好几个大版本,甚至还有些BUG一直延续到了最新的23ai版本。国产数据库的开发人员远没有ORACLE那么多,ORACLE都做成这样了,国产数据库要是说merge into里没有BUG我是不相信的。
- 本文作者: DarkAthena
- 本文链接: https://www.darkathena.top/archives/oracle-merge-into-some-bug
- 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处