暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

当DML语句中有一条数据报错时,如何让该DML语句继续执行?

DB宝 2019-01-31
1578


题目部分

在Oracle中,当DML语句中有一条数据报错时,如何让该DML语句继续执行?



     

答案部分



当一个DML语句运行的时候,如果遇到了错误,那么这条语句会进行回滚,就好像没有执行过。对于一个大的DML语句而言,如果个别数据错误而导致整个语句的回滚,那么会浪费很多的资源和运行时间。所以,从Oracle 10g开始Oracle支持记录DML语句的错误,而允许语句自动继续执行。这个功能可以使用DBMS_ERRLOG包实现。

 1LHR@orclasm > CREATE TABLE T1 AS SELECT ROWNUM A,ROWNUM B FROM DBA_SEGMENTS WHERE ROWNUM <=10;
2
3Table created.
4
5LHR@orclasm > CREATE TABLE T2 AS SELECT ROWNUM A,ROWNUM B FROM DBA_SEGMENTS WHERE ROWNUM <=20;
6
7Table created.
8
9LHR@orclasm > ALTER TABLE T1 ADD CONSTRAINT PK_T1_A PRIMARY KEY(A);
10
11Table altered.
12
13LHR@orclasm >  INSERT INTO T1 SELECT * FROM T2;
14 INSERT INTO T1 SELECT * FROM T2
15*
16ERROR at line 1:
17ORA-00001unique constraint (LHR.PK_T1_A) violated
18
19
20LHR@orclasm >  SELECT COUNT(1FROM T1;
21
22  COUNT(1)
23----------
24        10
25
26LHR@orclasm >  SELECT COUNT(1FROM T2;
27
28  COUNT(1)
29----------
30        20


可以看到,由于插入的数据违反了唯一性约束,导致了Oracle报错。下面创建记录DML错误信息的记录表,通过DBMS_ERRLOG包来进行创建,而这个包目前只包括这一个过程:

1LHR@orclasm > DESC DBMS_ERRLOG
2PROCEDURE CREATE_ERROR_LOG
3 Argument Name                  Type                    In/Out Default?
4 ------------------------------ ----------------------- ------ --------
5 DML_TABLE_NAME                 VARCHAR2                IN
6 ERR_LOG_TABLE_NAME             VARCHAR2                IN     DEFAULT
7 ERR_LOG_TABLE_OWNER            VARCHAR2                IN     DEFAULT
8 ERR_LOG_TABLE_SPACE            VARCHAR2                IN     DEFAULT
9 SKIP_UNSUPPORTED               BOOLEAN                 IN     DEFAULT


若不指定ERR_LOG_TABLE_NAME参数,则创建的记录错误日志的表名为:ERR$_原表名的前25个字符。利用CREATE_ERROR_LOG来创建T1表的DML错误记录表:

 1SQL> EXEC DBMS_ERRLOG.CREATE_ERROR_LOG('T1','T1_ERRLOG','LHR');
2
3PL/SQL procedure successfully completed
4LHR@orclasm > DESC T1_ERRLOG;
5 Name                                      Null?    Type
6 ----------------------------------------- -------- ----------------------------
7 ORA_ERR_NUMBER$                                    NUMBER
8 ORA_ERR_MESG$                                      VARCHAR2(2000)
9 ORA_ERR_ROWID$                                     ROWID
10 ORA_ERR_OPTYP$                                     VARCHAR2(2)
11 ORA_ERR_TAG$                                       VARCHAR2(2000)
12 A                                                  VARCHAR2(4000)
13 B                                                  VARCHAR2(4000)


可以看到Oracle创建的错误记录表包括错误号码ORA_ERR_NUMBER$,错误信息ORA_ERR_MESG$,记录的ROWID信息ORA_ERR_ROWID$,错误操作类型ORA_ERR_OPTYP$,错误标签ORA_ERR_TAG$,以及表中对应的列。

下面利用包含LOG ERROR语句的INSERT语句再次插入数据:

 1LHR@orclasm > INSERT INTO T1 SELECT * FROM T2 LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG_LHR')REJECT LIMIT UNLIMITED;
2
310 rows created.
4
5LHR@orclasm >  SELECT COUNT(1FROM T1;
6
7  COUNT(1)
8----------
9        20
10
11SELECT * FROM T1_ERRLOG;


 

可以看到,插入成功执行,但是插入记录为10条。从对应的错误信息表中已经包含了插入的信息。而且从错误信息表中还可以看到对应的错误号和详细错误信息,ORA_ERR_OPTYP$为错误操作类型,I表示为INSERT

关于LOG ERRORS的语法为,INTO语句后面跟随的就是指定的错误记录表的表名。在INTO语句后面,可以跟随一个表达式“('T1_ERRLOG_LHR')”即是ORA_ERR_TAG$中存储的信息,用来设置本次语句执行的错误在错误记录表中对应的TAG。有了这个语句,就可以很轻易的在错误记录表中找到某次操作所对应的所有的错误,这对于错误记录表中包含了大量数据,且本次语句产生了多条错误信息的情况十分有帮助。只要这个表达式的值可以转化为字符串类型就可以。REJECT LIMIT则限制语句出错的数量。

1LHR@orclasm > INSERT INTO T1 SELECT * FROM T2 LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT 1;
2INSERT INTO T1 SELECT * FROM T2 LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT 1
3*
4ERROR at line 1:
5ORA-00001unique constraint (LHR.PK_T1_A) violated


可以看到,当设置的REJECT LIMIT的值小于出错记录数时,语句会报错,这时LOG ERRORS语句没有起到应有的作用,插入语句仍然以报错结束。而如果将REJECT LIMIT的限制设置大于等于出错的记录数,则插入语句就会执行成功,而所有出错的信息都会存储到LOG ERROR对应的表中。只要指定了LOG ERRORS语句,不管最终插入语句十分成功的执行完成,在错误记录表中都会记录语句执行过程中遇到的错误。比如第一个插入由于出错数目超过REJECT LIMIT的限制,这时在记录表中会存在REJECT LIMIT + 1条记录数,因此这条记录错误导致了整个SQL语句的报错。如果不管碰到多少错误,都希望语句能继续执行,那么可以设置REJECT LIMITUNLIMITED。需要注意的是,即使做了回滚操作,错误日志表中的记录并不会减少,因为Oracle是利用自治事务的方式插入错误记录表的。

LOG ERRORS可以用在INSERTUPDATEDELETEMERGE后,但是,它有以下限制条件:

① 违反延迟约束。

② 直接路径的INSERTMERGE语句违反了唯一约束或唯一索引(注意:从Oracle 11g开始,已经取消了该条限制)。

③ 更新操作违反了唯一约束或唯一索引。

④ 错误日志表的列不支持的数据类型包括:LONGLONG RAWBLOGCLOBNCLOBBFILE以及各种对象类型。Oracle不支持这些类型的原因也很简单,这些特殊的类型不是包含了大量的记录,就是需要通过特殊的方法来读取,因此Oracle没有办法在SQL处理的时候将对应列的信息写到错误记录表中。

 


1.下面通过实验来验证不支持的操作

首先看一下违反延迟约束:

 1LHR@orclasm > ALTER TABLE T1 ADD CONSTRAINT PK_T1_B CHECK (B IS NOT NULL) DEFERRABLE INITIALLY DEFERRED;
2
3Table altered.
4
5LHR@orclasm > INSERT INTO T1 VALUES('21',''LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED;
6
71 row created.
8
9LHR@orclasm > commit;
10commit
11*
12ERROR at line 1:
13ORA-02091transaction rolled back
14ORA-02290check constraint (LHR.PK_T1_B) violated


由于延迟约束的检查在COMMIT时刻进行,而不是在DML发生的时刻,因此不会利用LOG ERRORS语句将违反结果的记录插入到记录表中,这也是很容易理解的。

下面看看直接路径违反唯一约束的情况:

 1LHR@orclasm > MERGE /*+append*/  INTO T1 T 
2  2  USING T1   
3  3  ON (T1.B=T.B)  
4  4  WHEN  MATCHED THEN 
5  5    UPDATE   SET T.A=1
6  6  LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED;
7
820 rows merged.
9LHR@orclasm > MERGE /*+append*/  INTO T1 T 
10  2  USING T1   
11  3  ON (1<>1)  
12  4  WHEN NOT MATCHED THEN 
13  5   INSERT (a,b) VALUES (1,1)
14  6  LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED;
15
160 rows merged.
17
18LHR@orclasm >    


可见,从Oracle 11g开始已经取消了该条限制。

最后来看看更新语句违反唯一约束的情况:

1LHR@orclasm > UPDATE T1 SET A='1' WHERE A='2' LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED;
2UPDATE T1 SET A='1' WHERE A='2' LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED
3*
4ERROR at line 1:
5ORA-00001unique constraint (LHR.PK_T1_A) violated


可以看到,如果更新操作导致了唯一约束或唯一索引冲突,是不会记录到错误记录表中的。

 

2.下面我们来看不支持的数据类型

 

 1LHR@orclasm > DROP TABLE T1_ERRLOG PURGE;
2
3Table dropped.
4
5LHR@orclasm >  alter table T1 add c clob;
6
7Table altered.
8
9LHR@orclasm >  EXEC DBMS_ERRLOG.CREATE_ERROR_LOG('T1','T1_ERRLOG');
10BEGIN DBMS_ERRLOG.CREATE_ERROR_LOG('T1','T1_ERRLOG'); END;
11
12*
13ERROR at line 1:
14ORA-20069: Unsupported column type(s) found: C
15ORA-06512: at "SYS.DBMS_ERRLOG", line 237
16ORA-06512: at line 1


可以看到,由于T1表拥有不支持的列,导致创建错误记录表的过程报错,错误提示就是T1表中包含了不支持的列。如果手工添加CLOB字段到错误记录表:

 1LHR@orclasm > alter table T1 DROP (c);
2
3Table altered.
4
5LHR@orclasm > EXEC DBMS_ERRLOG.CREATE_ERROR_LOG('T1','T1_ERRLOG');
6
7PL/SQL procedure successfully completed.
8
9LHR@orclasm > alter table T1 add c clob;
10
11Table altered.
12
13LHR@orclasm > alter table T1_ERRLOG add c clob;
14
15Table altered.


执行插入语句:

 1LHR@orclasm > INSERT INTO T1 VALUES('21','21','TEST'LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED;
2INSERT INTO T1 VALUES('21','21','TEST'LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED
3                                                        *
4ERROR at line 1:
5ORA-38904: DML error logging is not supported for LOB column "C"
6
7LHR@orclasm >  UPDATE T1 SET A='22' WHERE A='2' LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED;
8 UPDATE T1 SET A='22' WHERE A='2' LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED
9                                                  *
10ERROR at line 1:
11ORA-38904: DML error logging is not supported for LOB column "C"


可以看到,Oracle会直接报错。

1LHR@orclasm >  alter table T1_ERRLOG DROP (c);
2
3Table altered.
4
5LHR@orclasm > INSERT INTO T1 VALUES('1','1','TEST' ) LOG ERRORS INTO T1_ERRLOG('T1_ERRLOG')REJECT LIMIT UNLIMITED;
6
70 rows created.


可以看到,删除错误记录语句所不支持的列后,LOG ERRORS语句反而可以顺利执行,而且无论DML语句是否包括哪些不支持列的数据。

& 说明:

有关DBMS_ERRLOG更多内容介绍可以参考我的BLOGhttp://blog.itpub.net/26736162/viewspace-2144970/

 


本文选自《Oracle程序员面试笔试宝典》,作者:李华荣。







---------------优质麦课------------

 详细内容可以添加麦老师微信或QQ私聊。



About Me:小麦苗

 本文作者:小麦苗,只专注于数据库的技术,更注重技术的运用

● 作者博客地址:http://blog.itpub.net/26736162/abstract/1/

 本系列题目来源于作者的学习笔记,部分整理自网络,若有侵权或不当之处还请谅解

 版权所有,欢迎分享本文,转载请保留出处

 QQ:646634621  QQ群:618766405

 提供OCP、OCM和高可用部分最实用的技能培训

● 题目解答若有不当之处,还望各位朋友批评指正,共同进步

长按下图识别二维码或微信扫描下图二维码来关注小麦苗的微信公众号:xiaomaimiaolhr,学习最实用的数据库技术。


最后修改时间:2020-01-10 20:27:16
文章转载自DB宝,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论