如果PL/SQL 发生了一个错误,无论是系统错误还是应用的错误,都会抛出一个异常。当前PL/SQL块中执行单元就会暂定处理,如果当前块有一个异常处理单元的话,控制会转移到当前块的异常处理单元来处理异常。完成了异常处理后就不能再返回到当前块,相反,控制会转移到外层包围块,如果有的话。
异常分类:
- 系统异常
- 程序员定义异常
定义异常
定义异常名称
语法如下:
exception_name EXCEPTION;
有两种引用方式:
- 在程序的执行单元中的抛出异常RAISE:
RAISE exception_name;
- 在异常处理单元中的WHEN语句:
WHEN exception_name THEN
异常名称与错误代码关联
Oracle只给一小部分异常定义了名字;数据库里还有其他的错误只定义了一个错误数字和消息;
可以使用EXCEPTION_INIT编译命令,把异常名称和内部错误代码关联在一起:
例如:
PROCEDURE my_procedure
IS
invalid_month EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_month, -1843);
BEGIN
...
EXCEPTION
WHEN invalid_month THEN
错误数字可以使用符合下列约束的任意整数:
- 不可以使用 -1403
- 不可以使用0 或者除了 100 之外的任何正数
- 不可以使用小于 -1000000 的负数
命名的系统异常
最重要的也是最常用的命名异常可以在PL/SQL的 STANDARD 包中找到。
例如,代码中处理 NO_DATA_FOUND 异常:
WHEN NO_DATA_FOUND THEN
或者
WHEN STANDARD.NO_DATA_FOUND THEN
STANDARD包中预定义的一些异常
异常的作用范围
- 被命名的系统异常 —— 全局可用;可以在任何代码中抛出和处理被命名的系统异常
- 被命名的程序定义的异常 —— 只能在声明它的块的执行单元,异常处理单元被抛出和处理
- 匿名系统异常 —— 可以在任何PL/SQL块的异常处理单元的WHEN OTHERS 部分处理。如果这些成为了被指定名字的异常,这个名称的作用范围和被命名的自定义异常时一样的
- 匿名的自定义异常 —— 只在调用 RAISE_APPLICATION_ERROR 时才定义,并返回到调用程序
抛出异常
有三种方法抛出异常:
- 数据库检测到错误时可以抛出异常
- 使用RAISE语句抛出异常
- 使用内置的RAISE_APPLICATION_ERROR过程抛出异常
使用RAISE语句
语法如下:
RAISE exception_name;
RAISE package_name.exception_name;
RAISE;
第3种形式只可以用在异常处理单元的WHEN语句中,用于在异常处理单元中再次抛出同一个异常:
例如:
EXCEPTION
WHEN NO DATA fOUND
THEN
...
-- 现在再把未处理的NO_DATA_fOUND 传播到外层包围块中
RAISE;
使用RAISE_APPLICATION_ERROR过程
使用这个过程的好处在于,可以给异常加上一段错误消息。
一旦这个过程运行,当前PL/SQL块的执行就立即终止,对OUT和IN OUT参数所做的修改会被撤销,对于全局数据结构做的修改,如包变量,数据库对象不会回滚。用户必须明确调用 ROLLBACK 来撤销DML操作引起的修改。
语法如下:
PROCEDURE RAISE_APPLICATION_ERROR(
num binary_integer,
msg varchar2,
keeperorstack boolean default FALSE
);
- num是错误号,数值必须在 -20999 和 -20000 之间
- msg是错误消息
- keeperorstack表明是否把这个错误添加到栈中(TRUE)还是替换现存的错误(FALSE)
处理异常
一旦有异常抛出,当前PL/SQL块就会终止正常执行,把控制传递给异常处理单元。这个异常或者被当前PL/SQL块中的处理句柄处理,或者抛给外层块。
要捕获或者处理某个异常,必须专门为这个异常写一个异常句柄。异常处理代码在程序中的位置是在所有可执行语句之后,在块的END语句之前。
EXCEPTION关键字指示了异常处理单元的开始以及每个独立的异常句柄:
DECLARE
...declarations...
BEGIN
...executable statements...
[ EXCEPTION
...exception handles... ]
END;
异常句柄(exception handles)的语法:
WHEN exception_name [ OR exception_name ... ]
THEN
executable statements
或者
WHEN OTHERS
THEN
executable statements
- 一个异常处理单元可以有多个异常句柄。
- 可以通过OR操作符在一个 WHEN 语句中整合多个异常(不能使用AND)
- WHEN 语句只能根据异常名称而不能根据错误代码来捕获异常。
- WHEN OTHERS语句是可选的;WHEN OTHERS语句必须是异常处理部分的最后一个句柄。如果其后还有WHEN语句,会收到编译错误。
内置的错误函数
- SQLCODE —— 返回代码中最后一次抛出的错误;如果没有任何错误,返回0;如果在异常句柄之外调用,也返回0
- SQLERRM —— 返回某个错误代码对应的错误信息。若没有给其传递错误代码,就会返回SQLCODE的错误代码对应的消息
- DBMS_UTILITY.FORMAT_ERROR_STACK —— 和SQLERRM类似,但其只返回SQLCODE错误代码关联的消息。不能用来获得任意错误代码的消息
- DBMS_UTILITY.FORMAT_ERROR_BACKTRACE —— 返回一个格式化的文本串,这个文本串即程序栈,以及能够回退到错误最初发生代码行的行号
- DBMS_UTILITY.FORMAT_CALL_STACK —— 显示PL/SQL程序的调用堆栈
未处理异常
处理异常后继续执行后续代码的方法
一旦PL/SQL块中抛出了异常,正常执行单元被终止然后控制传递到异常处理单元。一旦块中有异常抛出就再也不能回到该块的执行单元了。
然而,在某些时候,我们想要异常过后还能继续执行的能力。
采用如下方法,可以保证未抛出异常的语句都有机会得到执行:
例如:
PROCEDURE change_date IS
BEGIN
BEGIN
DELETE FROM employees WHERE...;
EXCEPTION
WHEN OTHERS THEN log_error;
END;
BEGIN
UPDATE company SET...;
EXCEPTION
WHEN OTHERS THEN log_error;
END;
BEGIN
INSERT INTO company_history SELECT • FROM company WHERE...;
EXCEPTION
WHEN OTHERS THEN log_error;
END;
END;
在执行单元抛出的异常总是在当前块中被处理 —— 如果匹配的句柄存在。通过给任何语句加上一个BEGIN,之后加上一个EXCEPTION单元和END语句构建一个“虚拟块”出来,可以在代码中控制异常的失败范围。
WHEN OTHERS的处理
WHEN OTHERS语句可以捕获所有其他的未处理异常。
可以利用内置异常函数比如SQLCODE 和 DBMS_UTILITY.FORMAT_ERROR_STACK获得发生的错误的信息。