Thomas Zhang的杂货铺
03 07, 2008
编写健壮的PL/SQL代码(七)
作者 tomszrp 22:54 | Permalink 静态链接网址 | Comments 最新回复 (0) | Trackback 引用 (0) | 解决方案

当我们提交的PL/SQL代码具有很好的一直性、可读性和严密性时,往往会给运维DBA、其他程序员和最终用户一个惊喜。不管我们之前的业务逻辑设计的如何规范、清晰;也不管我们的业务操作流程定义多么明确,请始终坚信:最终用户在决大多数情况下的操作方法都不会符合业务规则的。

所以,我们要想编写出健壮的PL/SQL代码,就必须借助调试技术,可以在关键点上提示最终用户的错误.

 

很多做运维的DBA都遇到过类似的问题,因为用户的要求,当DBA在做数据维护时,偶尔会清理或修改一些比较重要的关系数据,往往会导致很多的代码执行异常,并且没有任何可明确的提示,DBA不得不打开代码去阅读、分析定位问题。所以,错误处理的细节可以确保迅速定位被破坏的数据或关系。

 

这里说到Oracle的错误有两种:编译时运行时错误。

下面我先用2个小的匿名块来对比一下编译时错误和运行时错误的区别。

1)编译时错误

   SQL> declare
      2   v_num number;
      3  begin
      4   v_num:=to_number('1A')
      5  end;
      6  /       
   ORA-06550: line 5, column 1:
   PLS-00103: Encountered the symbol "END" when expecting one of the following:
    
       . ( * % & = - + ; < / > at in is mod remainder not rem
        <> or != or ~= >= <= <> and or like LIKE2_
       LIKE4_ LIKEC_ between || multiset member SUBMULTISET_
   The symbol ";" was substituted for "END" to continue.    
   SQL> 

在这段代码的第4行,我故意少写了一个分号(;),但错误提示却是在line 5,这也是经常困惑大家的地方,在这里需要提醒大家的是,PL/SQL语法分析器指向的通常是出现错误的下一行,所以有时候我们在定位问题时,不要被表象或迷惑。再看一段代码,在下面的这段代码中,我增加了一个统计语句,但实际上non_table这个表在数据库中根本不存在,可以看到,下面的错误提示还是出现在了我故意漏写的标点符号上。

    SQL> declare
      2   v_num number; 
      3  begin
      4   select count(*) into v_num from non_table;   --non_table是一个不存在的表
      5   if v_num = 0 then
      6      v_num:=to_number('1A')
      7   end if;
      8  end;
      9  /
     end if;
     *
    ERROR at line 7:
    ORA-06550: line 7, column 2:
    PLS-00103: Encountered the symbol "END" when expecting one of the following:
    . ( * % & = - + ; < / > at in is mod remainder not rem
     <> or != or ~= >= <= <> and or like LIKE2_
    LIKE4_ LIKEC_ between || multiset member SUBMULTISET_
    The symbol ";" was substituted for "END" to continue.        
    SQL> 

为什么会出现这个现象,我想大家也可能都很明白:这是因为在调用SQL语法分析器检查内部的SQL语句之前,PL/SQL语法分析器会先对整个PL/SQL程序进行遍历检查。也就是说:PL/SQL总是会先于SQL进行语法检查,只有当PL/SQL语法按照语义上解释是正确的情况下,执行PL/SQL才会显示SQL语法错误(如果有的话)

 

提醒:在编写PL/SQL代码时,要时刻保持清醒,否则很容易出现编译时错误,编译时错误是不能调试的,只能借助PL/SQL语法分析器去定位编译时错误。

我经常看到有的开发人员一下子蒙头写了几百行PL/SQL代码(还很自诩,觉得自己很了不起,多插一句,往往很多负责开发的manager本身就不是特别懂技术,流程就...,看到team中的人,如此高效的开发能力,颇为赞赏,结果大家一联调测试时才发现,根本不能用,于是又花更多的时间、人力去处理这个早就应该OK的issue,实在可笑,这样的情况,我想国内很多的开发team中屡见不鲜吧),结果发现死活调试不通,最后不得不得花更多的时间去分析、定位他的代码中的问题,甚至有些情况下,不得不重新编写。

 

2)运行时错误

 

接着再看一个运行时错误,我还以上面的代码为例,大家对比一下就可以发现不同了:

    SQL> declare
      2   v_num number; 
      3  begin
      4   v_num:=to_number('1A'); 
      5  end;
      6  /
    declare
    *
    ERROR at line 1:
    ORA-06502: PL/SQL: numeric or value error: character to number conversion error
    ORA-06512: at line 4    
    SQL> 
很明显,这段修改过的代码通过了编译,但在运行时却发生了异常:试图将一个带字母的字符串转换成数字。并且,我们可以看到,PL/SQL分析器能准确的找到导致运行时出现异常的那行代码(如上 line 4),运行时异常是由运行时错误引起的。

捕获、跟踪、识别并正确处理这些异常,正是我们能写出健壮的PL/SQL代码所关心的。

3)系统预定义的标准异常

下表给出了Oracle提供了一些标准的预定义异常,绝大多数大家都面熟.

异常错误返回值描述

ACCESS_INTO_NULL

ORA-06530试图访问一个未初始化的对象
CASE_NOT_FOUNDORA-06592定义了一个case语句却没有else子句与之对应,并且没有一个case语句符合运行时的条件
COLLECTION_IS_NULLORA-06531试图访问一个未初始化的嵌套表(nested table)或可变数组(varry)
CURSOR_ALREADY_OPENORA-06511试图打开一个已经open的cursor
DUP_VAL_ON_INDEXORA-00001试图在表中唯一索引对应的列上插入一个重复值
INVALID_CURSORORA-01001试图进行一个非发的cursor操作,比如关闭一个已经closed游标等
INVALID_NUMBERORA-01722非法数字
LOGIN_DENIEDORA-01017试图用非法的用户名或口令登陆
NO_DATA_FOUNDORA-01403使用select - into结构并且返回为null;或访问嵌套表中已经删除的元素;或访问索引表中一个未初始化的元素
NOT_LOGGED_ONORA-01012程序在非连接状态发出一个数据库调用,一般发生在会话已经断开的情况下
PROGRAM_ERRORORA-06501通常表示所有Oracle为正式收录的错误。许多数据库的对象特性会产生这种错误
ROWTYPE_MISMATCHORA-06504游标结构与PL/SQL游标变量不一致;或游标参数的实际内容与形参不符
SELF_IS_NULLORA-30625在对象类型的一个实例还没有初始化的情况下,就试图调用他的非静态成员函数
STORAGE_ERRORORA-06500SGA运行时内存益处或SGA被破坏
SUBSCRIPT_BEYOND_COUNTORA-06533分配给嵌套表或可边数组的空间小于下标使用的值
SUBSCRIPT_OUTSIDE_LIMITORA-06532使用非法的索引值,来访问嵌套表或可变数组,例如:负数
SYS_INVALID_ROWIDORA-01410试图将一个字符串变为一个非法的ROWID值
TIMEOUT_ON_RESOURCEORA-00051数据库不能确保对一个资源的锁定
TOO_MANY_ROWSORA-01422使用select-into语句,但查询结果返回不止一行
VALUE_ERRORORA-06502试图把变量A赋给B,但变量B由于空间太小而不能保存
ZERO_DIVIDEORA-01476除数为0
OTHERSOracle用这个"others"捕捉所有未知的错误.可以使用sqlcode函数,sqlerrm函数在错误处理模块中显示错误 代码和错误信息.而且others处理必须是一个块的最后一个错误处理,否则others处理的优先级比较高,它会捕捉所有错误,包括预定义的Oracle错误和非预定义的Oracle错误

4)Oracle的两个内置函数

函数Oracle预定义的错误用户自定义的错误

SQLCODE

异常发生的时候,返回一个与Oracle预定义的异常对应的负值;只有NO_DATA_FOUND异常的返回值为正100

如果EXCEPTION_INIT PRAGAM未定义,返回1

如果EXCEPTION_INIT PRAGAM已定义,则返回一个在-20001到-20999之间的值

SQLERRM

过载函数。并按照下面的条件执行:

如果没有传递数字参数给函数,则返回触发的异常已经定义好的错误代码和信息

如果传递给函数的是一个正整数或是一个不属于Oracle预定义异常编号的负数,则返回实参的负数值和非Oracle异常的消息

如果传递给函数的参数是属于Oracle预定义的异常编号,则返回实参和Oracle预定义的消息

如果是由raise命令触发,则返回1和"User_Defined_Exception"的消息,否则返回一个在-20001和-20999之间的合法整数值和由RAISE_APPLICATION_INFO函数指定的文本消息

5)异常管理

PL/SQL代码中的异常捕获和管理也是一个经常让人犯晕的地方,在pl/sql中,除了系统支持的预定义异常外,也允许用户自定义异常,用户自定义的错误也可以有2种方式实现,一种是使用EXCEPTION_INIT指令的预编译程序,另外一种是不使用预编译程序。这部分内容网上的资料很多,也很详细,我就不再唠叨了。

如果要深入理解这部分内容,需要先了解Oracle内部的错误堆栈的管理,但错误堆栈是一块极其复杂的内容,不过我们只要始终牢记:堆栈顶部的程序触发错误,而底部的程序是发生错误的地方,问题就容易理解了。幸运的是,在Oracle 10g中,Oracle对dbms_utility这个程序包做了改进,10g中的dbms_utility.format_error_backtrace过程为记录和跟踪错误序列提供了一定的灵活性,也为Oracle PL/SQL开发者带来了便利。甲骨文公司对Oracle软件的不懈的人性化的更新和发展,是大家无法舍弃她的一个很重要的原因。

下面我们看一个如何在Exception Block中捕获异常发生所在行的例子:

在这篇小技巧中,介绍一下如何在Exception Block中捕获错误发生的所在行,以便于我们准确的定位和解决问题。在实际的pl/sql开发过程中,有的pl/sql代码非常庞大,甚至彼此嵌套了好多层,当然了,Exception部分也就变的复杂起来。

 

这个时候,我们在日常运维或开发调试的时候,经常会根据Exception Block中抛出的raise去分析是程序的问题还是数据的问题,以便于后期改进和处理。那么有的人就很头疼,一组上K行的pl/sql,分析起来也是很头疼的,他们就希望能在出现异常的时候,知道准确的出错SQL所在行。

 

以前我们一般就简单的通过SQLCODE、SQLERRM来获取一些错误代码和相关的信息,从Oralce 10g开始,我们可以借助DBMS_UTILITY.FORMAT_ERROR_BACKTRACE() 函数来实现我们的目标。

 

下面我通过一个简单的DEMO来说一下如何调用这个函数:

conn study/study;
create table zrp (no int);
set serveroutput on

declare
  v_no number;
begin
  select no into v_no from zrp;
exception
  when others then
     dbms_output.put_line('SQL CODE:'||sqlcode||chr(10)||
                           sqlerrm||chr(10)||
                           dbms_utility.format_error_backtrace());
end;
/
测试一下看看:
SQL> declare
  2    v_no number;
  3  begin
  4    select no into v_no from zrp;
  5  exception
  6    when others then
  7       dbms_output.put_line('SQL CODE:'||sqlcode||chr(10)||
  8                             sqlerrm||chr(10)||
  9                             dbms_utility.format_error_backtrace());
 10  end;
 11  /
SQL CODE:100
ORA-01403: no data found
ORA-06512: at line 4

PL/SQL procedure successfully completed
SQL>  
是不是很好用?

Comments
博客日历
« 八月 2008 »
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
搜索
最新发表
文章分类
文章归档
网站链接
新闻聚合