设为首页 收藏本站
查看: 454|回复: 0

[经验分享] FORALL 之 SAVE EXCEPTIONS 子句应用一例

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-7-9 09:26:40 | 显示全部楼层 |阅读模式
  对于大批量的DML操作中出现的错误,除了使用DML error logging特性来记录在DML期间出现的错误之外,使用批量SQL语句FORALL的SAVE
EXCEPTIONS是不错的选择之一。DML error logging特性的使用较FORALL之 SAVE EXCEPTIONS相对简单,也存在一些不足,如每一个被操作的DML
对象需要创建相应的对应的日志表,不利于集中管理。本文对DML error logging这个不利于集中管理的特性使用FORALL 之 SAVE EXCEPTIONS
方式来完成。

   下面的示例来自一个实际的应用,撇开原始的表名与实际应用中的处理过程,仅仅通过简单示例来展现。
      1、两个不同的DB,假定设定为数据库A和数据库B(为简化,在同一个DB上来演示)。
      2、需要将数据库A的一些表的数据同步到数据库B对应的目的表
      3、如果同步的过程中出现某条特定的记录错误,则写该记录产生的错误信息(含表名,主键)到日志表,并将其原表同步状态更新为N,否则更新为Y
      4、如果非由于INSERT产生的错误信息,则要求写过程名及对应的错误信息到日志表

   如对于批量SQL较为熟悉,请直接阅读下文,否则,请参阅阅读本文所需要的相关知识:

      批量SQL之 FORALL 语句
      批量SQL之 BULK COLLECT 子句
      PL/SQL 集合的初始化与赋值
      PL/SQL 联合数组与嵌套表
      PL/SQL 变长数组
      PL/SQL --> PL/SQL记录

一、创建演示环境
为简化,下面的演示代码在同一个数据库上完成,在不同的DB上来完成仅仅是需要设定DB LINK而已。


    -->创建用于演示的源表emp_source,其数据来自scott.emp  
    scott@CNMMBO> create table emp_source as select empno,ename,sal from emp;   
      
    Table created.  
      
    -->为源表增加一个字段is_sync,用于记录是否同步成功  
    scott@CNMMBO> alter table emp_source add is_sync char(1);  
      
    Table altered.  
      
    -->创建目的表  
    scott@CNMMBO> create table emp_dest as select empno,ename,sal from emp_source where 1=0;  
      
    Table created.  
      
    -->创建记录错误信息的日志表  
    scott@CNMMBO> create table err_log_tbl(log_seq number(12) not null, log_time date not null,  
      2  sp_name varchar2(100),table_name varchar2(30),table_pk varchar2(30),err_msg varchar2(2000));  
      
    Table created.  
      
    -->为表emp_dest 添加约束用于在insert过程中触发错误产生  
    scott@CNMMBO> alter table emp_dest add constraint ck_sal check(sal>500);  
      
    Table altered.  
      
    scott@CNMMBO> alter table emp_dest modify(ename constraint nn_ename not null);  
      
    Table altered.  
      
    -->创建一个用于错误日志表上的sequence  
    CREATE SEQUENCE SCOTT.ERR_SEQ  
      START WITH 20  
      MAXVALUE 999999999999999999999999999  
      MINVALUE 0  
      NOCYCLE  
      NOCACHE  
      NOORDER;  
      
    -->创建一个函数用于获取sequence  
    CREATE OR REPLACE FUNCTION SCOTT.gen_new_err_seq  
       RETURN err_log_tbl.log_seq%TYPE  
    IS  
       newrecid   err_log_tbl.log_seq%TYPE;  
    BEGIN  
       SELECT ERR_SEQ.NEXTVAL INTO newrecid FROM DUAL;  
      
       RETURN newrecid;  
    END;  
    /  

二、使用下面的PL/SQL块演示


    -->下面的演示代码可以封装到包  
    DECLARE  
       c_sp_name        CONSTANT VARCHAR2 (50) := 'anonymity_plsql_block';  
       c_process_name   CONSTANT VARCHAR2 (20) := 'ins_emp_dest';  
       c_table_name              VARCHAR2 (30) := 'emp_dest';  
       debugpos                  bo_common_pkg.debug_pos_type := 0;  
       v_limit                   PLS_INTEGER := 5;    -->定义游标fetch时提取数量的限制数,由于emp_source记录较少,此处设定为5  
       err_msg                   VARCHAR2 (1000);  
      
       CURSOR cur_emp                                 -->声明游标从源表取数据  
       IS  
          SELECT empno, ename, sal FROM emp_source WHERE is_sync IS NULL;  
      
       TYPE emp_tab_type IS TABLE OF cur_emp%ROWTYPE;  
      
       emp_tab                   emp_tab_type;      -->声明基于游标的嵌套表  
      
       TYPE err_rec_type IS TABLE OF err_log_tbl%ROWTYPE;     
      
       err_tab                   err_rec_type := err_rec_type (); -->声明基于err_log_tab的嵌套表并初始化  
       sub_proc_exp              EXCEPTION;     -->定义了两个异常  
       bulk_error                EXCEPTION;  
       PRAGMA EXCEPTION_INIT (bulk_error, -24381);  
    BEGIN  
       debugpos   := 10;  
      
       OPEN cur_emp;  
      
       LOOP  
          BEGIN  
             debugpos            := 20;  
      
             FETCH cur_emp                -->使用游标fetch数据并存放到嵌套表  
             BULK COLLECT INTO emp_tab  
             LIMIT v_limit;  
      
             EXIT WHEN emp_tab.COUNT = 0;  
             debugpos            := 30;  -->下面几个赋值语句用于模拟insert产生error信息  
             emp_tab (2).ename   := RPAD (emp_tab (2).ename, 15, '*');   
             emp_tab (3).sal     := 100;  
             emp_tab (7).sal     := NULL;  
             debugpos            := 40;  
      
             FORALL i IN 1 .. emp_tab.COUNT  -->FORALL子句插入数据,使用SAVE EXCEPTIONS子句  
             SAVE EXCEPTIONS  
                INSERT INTO emp_dest  
                     VALUES emp_tab (i);  
          EXCEPTION  
             WHEN bulk_error  
             THEN                            -->下面是对bulk_error时的处理  
                debugpos   := 50;  
      
                FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT  
                LOOP                        -->FOR 循环中对用于生成插入err_log_tbl所需的数据信息  
                   err_tab.EXTEND;  
                   err_tab (i).log_seq      := gen_new_err_seq;  
                   err_tab (i).log_time     := SYSDATE;  
                   err_tab (i).sp_name      := c_process_name;  
                   err_tab (i).table_name   := c_table_name;  
                   err_tab (i).table_pk     := TO_CHAR (emp_tab (SQL%BULK_EXCEPTIONS (i).ERROR_INDEX).empno);  
                   err_tab (i).err_msg      := SUBSTR (SQLERRM (-SQL%BULK_EXCEPTIONS (i).ERROR_CODE), 1, 300);  
                END LOOP;  
      
                debugpos   := 60;  
      
                FORALL i IN 1 .. err_tab.COUNT   -->将error信息插入到err_log_tbl  
                   INSERT INTO err_log_tbl  
                        VALUES err_tab (i);  
      
                debugpos   := 70;  
      
                FOR i IN 1 .. err_tab.COUNT     -->对于未成功插入的记录,更新源表以表明同步失败  
                LOOP  
                   UPDATE emp_source e  
                      SET is_sync   = 'N'  
                    WHERE EXISTS  
                             (SELECT 1  
                                FROM err_log_tbl d  
                               WHERE e.empno = d.table_pk);  
                END LOOP;  
             WHEN OTHERS  
             THEN  
                err_msg   := SUBSTR (SQLERRM, 1, 200);  
                RAISE sub_proc_exp;  
          END;  
       END LOOP;  
      
       CLOSE cur_emp;  
      
       debugpos   := 80;  
      
    -->Author: Robinson Cheng  
    -->Blog  : http://blog.iyunv.com/robinson_0612  
      
       UPDATE emp_source e     -->对于所有成功同步的数据更新源表的标志位  
          SET is_sync   = 'Y'  
        WHERE EXISTS  
                 (SELECT 1  
                    FROM emp_dest d  
                   WHERE e.empno = d.empno)  
              AND is_sync IS NULL;  
    EXCEPTION       -->外层exception处理error信息,并写入到err_log_tbl日志文件  
       WHEN sub_proc_exp  
       THEN  
          err_msg      :=  
                SUBSTR ('<<Err @ ' || c_sp_name || ' - Debug Pos : ' || TO_CHAR (debugpos) || '>> - ' || SQLERRM, 1, 500)  
             || ' '  
             || err_msg;  
      
          INSERT INTO err_log_tbl  
               VALUES (gen_new_err_seq,  
                       SYSDATE,  
                       c_sp_name,  
                       NULL,  
                       NULL,  
                       err_msg);  
      
          DBMS_OUTPUT.put_line ('Error msg is : ' || err_msg);  
       WHEN OTHERS  
       THEN  
          err_msg      :=  
             SUBSTR ('<<Err @ ' || c_sp_name || ' - Debug Pos : ' || TO_CHAR (debugpos) || '>> - ' || SQLERRM, 1, 500);  
      
          INSERT INTO err_log_tbl  
               VALUES (gen_new_err_seq,  
                       SYSDATE,  
                       c_sp_name,  
                       NULL,  
                       NULL,  
                       err_msg);  
      
          DBMS_OUTPUT.put_line ('Error msg is : ' || err_msg);  
    END;  
    /  
      
    1、非INSERT插入异常的处理  
    scott@CNMMBO> @test_bulk_ins_err       -->上面的演示代码被保存到@test_bulk_ins_err.sql文件  
    Error msg is : <<Err @ anonymity_plsql_block - Debug Pos : 30>> - User-Defined Exception ORA-06502:   
    PL/SQL: numeric or value error: character string buffer too small  
    上面的错误提示是由于我们声明的emp_tab嵌套表基于源表,因此出现数据缓冲太小的错误  
      
    PL/SQL procedure successfully completed.  
      
    2、内层exception中bulk_error处产生的异常  
       将代码debugpos:= 30; 之后的3行注释掉  
    scott@CNMMBO> alter table emp_source add constraint ck_is_sync check (is_sync in ('C','E'));  
      
    Table altered.  
      
    scott@CNMMBO> @test_bulk_ins_err  
    Error msg is : <<Err @ anonymity_plsql_block - Debug Pos : 80>> - ORA-02290: check constraint (SCOTT.CK_IS_SYNC) violated  
      
    PL/SQL procedure successfully completed.     
      
    上面由于源表的is_sync列有约束限制,只允许出现C或E值,而bulk_error中是更新为N,所以给出错误提示  
    我们来看看err_log_tbl,两条源表到目标表在非insert时产生的错误信息已经被记录到日志表  
    scott@CNMMBO> select * from err_log_tbl;  
      
    LOG_SEQ LOG_TIME  SP_NAME                TABLE_NAME   TABLE_PK   ERR_MSG  
    ------- --------- ---------------------- ------------ ---------- ---------------------------------------------  
         21 11-AUG-12 anonymity_plsql_block                          <<Err @ anonymity_plsql_block - Debug Pos : 3  
                                                                     0>> - User-Defined Exception ORA-06502: PL/SQ  
                                                                     L: numeric or value error: character string b  
                                                                     uffer too small  
      
         22 11-AUG-12 anonymity_plsql_block                          <<Err @ anonymity_plsql_block - Debug Pos : 8  
                                                                     0>> - ORA-02290: check constraint (SCOTT.CK_I  
                                                                                S_SYNC) violated  
                                                                                    
    3、FORALL子句在insert时产生的异常  
      
    scott@CNMMBO> alter table emp_source drop constraint ck_is_sync;  -->删除emp_source上is_sync列的check约束  
      
    Table altered.  
      
    scott@CNMMBO> @test_bulk_ins_err                                  -->将源表记录同步到目标表  
      
    PL/SQL procedure successfully completed.  
      
    scott@CNMMBO> select * from emp_dest;  
      
         EMPNO ENAME             SAL  
    ---------- ---------- ----------  
          7369 SMITH             800  
          7499 ALLEN            1600  
          7521 WARD             1250  
          7566 JONES            2975  
          7654 MARTIN           1250  
          7698 BLAKE            2850  
          7782 CLARK            2450  
          7788 SCOTT        34171.88  
          7839 KING             5000  
          7844 TURNER           1500  
          7876 ADAMS            1100  
          7900 JAMES             950  
          7902 FORD             3000  
          7934 MILLER           1300  
      
    14 rows selected.  
      
    scott@CNMMBO> select * from emp_source;                           -->可以看到目标表已存在数据且源表状态列被更新  
      
         EMPNO ENAME             SAL I  
    ---------- ---------- ---------- -  
          7369 SMITH             800 Y  
          7499 ALLEN            1600 Y  
          7521 WARD             1250 Y  
          7566 JONES            2975 Y  
          7654 MARTIN           1250 Y  
          7698 BLAKE            2850 Y  
          7782 CLARK            2450 Y  
          7788 SCOTT        34171.88 Y  
          7839 KING             5000 Y  
          7844 TURNER           1500 Y  
          7876 ADAMS            1100 Y  
          7900 JAMES             950 Y  
          7902 FORD             3000 Y  
          7934 MILLER           1300 Y  
      
    14 rows selected.  
      
    -->下面为源表增加3条记录以模仿在INSERT过程中出现的异常  
    scott@CNMMBO> insert into emp_source select 1111,'Robinson',2000,null from dual;  
      
    1 row created.  
      
    scott@CNMMBO> insert into emp_source select 2222,null,1000,null from dual;  
      
    1 row created.  
      
    scott@CNMMBO> insert into emp_source select 3333,'Jackson',100,null from dual;  
      
    1 row created.  
      
    scott@CNMMBO> commit;  
      
    Commit complete.  
      
    scott@CNMMBO> @test_bulk_ins_err   -->再次执行时发现只有empno号为1111的记录被插入,而2222和3333都由于错误而未被同步的目标表  
      
    PL/SQL procedure successfully completed.  
      
    scott@CNMMBO> select * from emp_dest where empno in (1111,2222,3333);  
      
         EMPNO ENAME             SAL  
    ---------- ---------- ----------  
          1111 Robinson         2000  
      
    scott@CNMMBO> select * from emp_source where empno in (1111,2222,3333);  
      
         EMPNO ENAME             SAL I  
    ---------- ---------- ---------- -  
          1111 Robinson         2000 Y  
          2222                  1000 N  
          3333 Jackson           100 N  
      
    -->检查错误日志,未成功插入的到目标表的记录写入到日志,含有表名以及主键,插入时对应的错误信息   
    scott@CNMMBO> select * from err_log_tbl;  
      
    LOG_SEQ LOG_TIME  SP_NAME                TABLE_NAME  TABLE_PK   ERR_MSG  
    ------- --------- ---------------------- ----------- ---------- ---------------------------------------------  
         21 11-AUG-12 anonymity_plsql_block                         <<Err @ anonymity_plsql_block - Debug Pos : 3  
                                                                    0>> - User-Defined Exception ORA-06502: PL/SQ  
                                                                    L: numeric or value error: character string b  
                                                                    uffer too small  
      
         22 11-AUG-12 anonymity_plsql_block                         <<Err @ anonymity_plsql_block - Debug Pos : 8  
                                                                    0>> - ORA-02290: check constraint (SCOTT.CK_I  
                                                                    S_SYNC) violated  
      
         23 11-AUG-12 ins_emp_dest           emp_dest    2222       ORA-01400: cannot insert NULL into ()  
         24 11-AUG-12 ins_emp_dest           emp_dest    3333       ORA-02290: check constraint (.) violated      

三、总结
1、上面的代码成功的实现了数据之间的同步问题(不同数据库使用DB LINK),且将同步期间的错误信息记录到日志表
2、对于多表需要同步的情形,上述方法便于集中管理错误信息。其次是源表使用状态标志位便于判断相应的记录同步成功的情况
3、同时结合了FORALL 与BULK COLLECT INTO批量SQL方法,且在使用游标打开集合时使用LIMIT子句来减小内存过度开销
4、使用了通过标识错误异常位置的debugpos变量来便于查找那个地方或那几行代码引发异常
5、注意处理error信息时,通过TO_CHAR (emp_tab (SQL%BULK_EXCEPTIONS (i).ERROR_INDEX).empno)得到表上主键的值

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.iyunv.com/thread-21847-1-1.html 上篇帖子: Oracle 阻塞(blocking blocked) 下篇帖子: DML Error Logging 特性
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表