zoukankan      html  css  js  c++  java
  • SQLERRM, use it with caution.

    问题由来

    客户发过来一个request, 说调用某个接口的时候出现了一堆的错误日志,但是从给出的信息很难知道什么地方出错了。从发过来的错误日志中可以看出一堆的‘ ORA-20002’ 的错误信息,但是没有具体的error message! 很显然, 这个是程序中自定义的错误,所以这个日志信息几乎是没有任何价值可言。

    不过幸运的是,从日志中可以判断出来出错的位置在于通过FORALL方式把一个集合(associate array)中的数据批量插入一张表,代码如下...

    begin
    forall i
    in g_insert_auth_table.first .. g_insert_auth_table.last save exceptions
    insert into gem_orig_authorization
    values g_insert_auth_table(i);
    exception
    when others then
    for err in 1 .. sql%bulk_exceptions.count
    loop
    add_update_imp( g_insert_imp_rowid_table(sql
    %bulk_exceptions(err).error_index),
    sqlerrm(
    -sql%bulk_exceptions(err).error_code) );
    pack_gem_common.log_generic(
    'flush_insert_auth',
    vv_message2
    => sqlerrm(-sql%bulk_exceptions(err).error_code),
    vv_message1
    => 'Error while inserting gem_orig_authorization of combination' ||
    g_insert_auth_table(sql
    %bulk_exceptions(err).error_index)
    .combination_id
    || '');
    end loop;
    end;

    错误信息显示的就是vv_message2 => sqlerrm(-sql%bulk_exceptions(err).error_code) 这个参数显示的。很显然,这个是通过SQLERRM函数来获取error message的,但是杯具的是因为-20002是用户自定义的错误,不是oracle标准的error code, 因此通过SQLERRM方式来获取error message是肯定不行的,

    SQL> begin
    2 dbms_output.put_line(sqlerrm(-20002));
    3 end;
    4 /
    ORA
    -20002:

    不过分析上面的这段代码,肯定是在插入数据的时候出现问题了,通过分析这张表上的trigger,最终定位到是数据违背了该表上定义的一个constraint! 虽然trigger中会抛出具体的错误信息,但是在捕获的时候因为用了错误的方式导致这个错误信息就丢失了!因此在使用SQLERRM的时候一定要注意,要判断error code是不是标准的oracle error code才行。

     从文档 http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/tuning.htm#sthref2201 中可以看到如下这段话...

    All exceptions raised during the execution are saved in the cursor attribute %BULK_EXCEPTIONS, which stores a collection of records. Each record has two fields:

    %BULK_EXCEPTIONS(i).ERROR_INDEX holds the iteration of the FORALL statement during which the exception was raised.

    %BULK_EXCEPTIONS(i).ERROR_CODE holds the corresponding Oracle error code.

    The values stored by %BULK_EXCEPTIONS always refer to the most recently executed FORALL statement. The number of exceptions is saved
    in %BULK_EXCEPTIONS.COUNT. Its subscripts range from 1 to COUNT.

    The individual error messages,
    or any substitution arguments, are not saved, but the error message text can looked up using ERROR_CODE with SQLERRM

     个人觉得oracle这点做得不够好,应该在SQL%BULK_EXCEPTIONS中再加入一个field,用来保存error_message, 这对于用户自定义的error message非常有帮助,否则像遇到的这个问题就会搞得error message丢失了!

    当然我们可以自己搞个workaround,自己定义一个error_message的table,用来保存用户自定义的error_message,这样在处理的时候就可以获得我们自己定义的error message了.... (当然这个例子不是很合理,完全可以用一个constraint来代替trigger, 这样就可以用SQLERRM了,这里只是演示作用)

    create table fang_error_msg(error_code int, error_msg varchar2(100));
    insert into fang_error_msg values(-20002, 'code should be less than 10!');
    commit;

    create table fang_test(code int);

    create trigger tri_fang_test
    before
    insert on fang_test
    for each row
    begin
    if :new.code > 10 then
    raise_application_error(
    -20002, 'blah blah...');
    end if;
    end;
    /

    declare
    type t_int_table
    is table of pls_integer index by pls_integer;
    l_code_table t_int_table;
    begin
    for i in 5..15 loop
    l_code_table(i) :
    = i;
    end loop;

    forall i
    in l_code_table.first..l_code_table.last save exceptions
    insert into fang_test values (l_code_table(i));

    exception
    when others then
    declare
    l_error_code pls_integer;
    l_error_message
    varchar2(2000);
    begin
    for err in 1..sql%bulk_exceptions.count loop
    l_error_code :
    = -sql%bulk_exceptions(err).error_code;

    if l_error_code between -20999 and -20000 then
    select error_msg into l_error_message from fang_error_msg where error_code=l_error_code;
    else
    l_error_message :
    = sqlerrm(l_error_code);
    end if;

    dbms_output.put_line(l_error_message);
    end loop;
    end;
    end;
    /

    关于SQLERRM (http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/sqlerrm_function.htm)

     SQLERRM是一个pl/sql function,而不是sql function, 因此不能用在SQL语句中,只有通过pl/sql方式来调用,如下所示...

    SQL> select sqlerrm(-1555) from dual;
    select sqlerrm(-1555) from dual
    *
    ERROR at line
    1:
    ORA
    -00904: "SQLERRM": invalid identifier


    SQL
    > begin
    2 dbms_output.put_line(sqlerrm(-1555));
    3 end;
    4 /
    ORA
    -01555: snapshot too old: rollback segment number with name "" too small

    文档上关于SQLERRM有这么一段notes...

    SQLERRM is especially useful in the OTHERS exception handler, where it lets you identify which internal exception was raised. The error number passed to SQLERRM should be negative. Passing a zero to SQLERRM always returns the ORA-0000: normal, successful completion message. Passing a positive number to SQLERRM always returns the User-Defined Exception message unless you pass +100, in which case SQLERRM returns the ORA-01403: no data found message.

    注意这里面有句话--Passing a positive number to SQLERRM always returns the User-Defined Exception message unless you pass +100, 也就是说所有不等于100的正的error code都会返回'User-Defined Exception'这样的error message, 这个与实际不相符,貌似只有error code为1的时候才会返回这个信息,其他值的时候会返回'-NNNNN: non-ORACLE exception'这样的信息...

    SQL> exec dbms_output.put_line(sqlerrm(1));
    User-Defined Exception

    PL
    /SQL procedure successfully completed.

    SQL
    > exec dbms_output.put_line(sqlerrm(2));
    -2: non-ORACLE exception

    PL
    /SQL procedure successfully completed.

    SQL
    > exec dbms_output.put_line(sqlerrm(3));
    -3: non-ORACLE exception

    PL
    /SQL procedure successfully completed.

    SQL
    > exec dbms_output.put_line(sqlerrm(10000));
    -10000: non-ORACLE exception

     比较特殊的几个“error code" -- 0, 100,

    SQL> exec dbms_output.put_line(sqlerrm(0));
    ORA
    -0000: normal, successful completion

    PL
    /SQL procedure successfully completed.

    SQL
    > exec dbms_output.put_line(sqlerrm(100));
    ORA
    -01403: no data found

    PL
    /SQL procedure successfully completed.

    SQL
    >

    如果error code是负数,但是不是valid的话,那么错误信息是‘ORA-NNNNN: Message NNNNN not found; product=RDBMS; facility=ORA’,

    SQL> exec dbms_output.put_line(sqlerrm(-2));
    ORA
    -00002: Message 2 not found; product=RDBMS; facility=ORA

    PL
    /SQL procedure successfully completed.

    SQL
    > exec dbms_output.put_line(sqlerrm(-3));
    ORA
    -00003: Message 3 not found; product=RDBMS; facility=ORA

    PL
    /SQL procedure successfully completed.

    SQL
    >

    关于DBMS_UTILITY.FORMAT_ERROR_STACK

    跟SQLERRM类似的还有一个函数,叫DBMS_UTILITY.FORMAT_ERROR_STACK,而且一般是推荐用这个函数来获取错误信息,因为这个函数返回的字符串最多支持2000个字符,而SQLERRM只有255个字符。

    DBMS_UTILITY.FORMAT_ERROR_STATCK不接受error code参数,因此返回的是最近抛出的一个错误的信息...

    SQL> begin
    2 raise NO_DATA_FOUND;
    3 exception
    4 when no_data_found then
    5 dbms_output.put_line(dbms_utility.format_error_stack);
    6 end;
    7 /
    ORA
    -01403: no data found

     如果没有错误抛出,直接调用这个函数,应该是啥都没有...

    SQL> exec dbms_output.put_line(nvl(dbms_utility.format_error_stack, 'nothing wrong happened!'));
    nothing wrong happened!

    PL
    /SQL procedure successfully completed.


  • 相关阅读:
    SilkTest天龙八部系列1-初始化和构造函数
    SilkTest天龙八部系列3-动态父窗口
    SilkTest天龙八部系列4-ChildWin
    java程序查不出数据来
    关于模拟器不能运行项目问题:Installation error: INSTALL_FAILED_INSUFFICIENT_STORAGE
    汉语-词语:寂静
    汉语-词语:幽静
    汉语-词语:恬静
    汉语-词语:静谧
    汉语-词语:冷静
  • 原文地址:https://www.cnblogs.com/fangwenyu/p/2100656.html
Copyright © 2011-2022 走看看