异常处理
📄字数 4.7K
👁️阅读量 加载中...
在 PL/SQL 中,异常处理Exception Handling
是用于捕获和处理程序执行过程中发生的错误的机制。合理的异常处理可以提高程序的健壮性和可维护性。异常包括预定义异常,非预定义异常(错误码)、自定义异常、以及异常处理函数等。
异常处理优点如下:
- 健壮性:使程序能够处理错误,避免程序突然崩溃,并向用户或调用者提供明确的错误信息。
- 可控性:允许程序员在错误发生时应该采取行动(如记录错误、回滚事务、尝试替代方案、通知用户等)。
- 事务完整性:通常在异常处理块中进行事务回滚 (ROLLBACK),确保数据库状态的一致性,避免部分更新。
- 代码可读性与可维护性:将错误处理逻辑集中到专门的代码块 (EXCEPTION块) 中,与主要业务逻辑 (BEGIN 块) 分离。
异常处理的基本流程如下:
程序在BEGIN
块中正常执行,如果发生错误(异常被引发或抛出RAISED
),程序会立即跳转到当前块的EXCEPTION
部分, 在EXCEPTION
部分,查找与该异常类型匹配的WHEN
子句。如果找到匹配的WHEN
子句,则执行该子句下的处理代码,处理完成后,程序继续执行EXCEPTION
块之后的语句;如果没有找到匹配的WHEN
子句,则异常会传播到外层调用块执行,或者最终由数据库自身处理。
异常处理结构
PL/SQL的异常处理结构基于BEGIN ... EXCEPTION ... END;
。
SQL
DECLARE
-- 声明部分 (变量、常量、游标、类型等)
-- 异常定义
BEGIN
-- 可执行部分 (主要业务逻辑代码)
-- 可能引发异常的语句在这里执行
EXCEPTION
-- 异常处理部分
WHEN exception_name1 THEN
-- 处理 exception_name1 异常的代码
statement1;
statement2;
...
WHEN exception_name2 THEN
-- 处理 exception_name2 异常的代码
...
WHEN OTHERS THEN
-- 处理所有未被前面WHEN子句捕获的异常的代码
...
END;
/
简要解释
DECLARE
(可选): 声明局部变量、常量、游标、类型等。BEGIN
: 包含程序的主要执行逻辑,异常可能发生的地方。EXCEPTION
: 关键字,异常处理部分。
-WHEN exception_name THEN
: 指定要捕获的异常类型。
-exception_name
可以是:XuguDB预定义的异常名称(如:ZERO_DIVIDE(除数为0)、NO_DATA_FOUND(查询无结果)、TOO_MANY_ROWS(查询结果太多)、DUP_VAL_ON_INDEX(违反唯一值约束))。也可以是:用户自定义的异常名称(需要在 DECLARE 部分声明),通过语法ExceptionDef
实现。
-WHEN OTHERS THEN
:用于捕获所有未被前面特定WHEN子句处理的异常,建议在关键代码块(尤其是涉及数据库修改的块)中总是包含 WHEN OTHERS,以确保没有未处理的异常导致不可预测的行为或数据不一致。
- 异常处理代码: 当匹配到 WHEN 子句时执行的语句块,通常包括记录日志、回滚事务 (ROLLBACK)、设置错误标志、通知用户等操作。END
: 结束整个 PL/SQL 块。
异常处理语句共包括三个部分:
- 异常定义
- 抛出异常
- 异常处理
语法定义
- 异常定义
参数说明
ExceptionDef
:用户自定义异常语句,使用关键字 EXCEPTION 在 DECLARE 中定义。exception_name
:用户自定义异常名称。
抛出异常
参数说明
ThrowStmt
:抛出异常语句,抛出异常语句在块语句体或过程体中使用,可使用关键字 RAISE 或 THROW。ColumnName
:异常名称。b_expr
:附带错误信息。
异常处理
- 参数说明:
- 可以存在多个 WHEN 子句,每个子句处理一种特定的异常,使用
WHEN OTHERS THEN
可以捕获所有未被捕获的异常。 ColumnName
:异常名称,如 TOO_MANY_ROWS、ZERO_DIVIDE 等。ICONST
:错误号。
- 可以存在多个 WHEN 子句,每个子句处理一种特定的异常,使用
异常类型
异常类型主要包括预定义异常、自定义异常、非预定义异常(错误码)、以及异常处理函数。
预定义异常
XuguDB 在 PL/SQL 内置了一系列常见的运行时错误对应的异常,具有标准的名称(如:NO_DATA_FOUND
)和关联的错误代码(如:19009
)。这些异常是隐式声明的,无需在 DECLARE 部分声明,可直接在 EXCEPTION 块的 WHEN 子句中使用。
预定义异常如下表所示:
数据库 | 异常标识 | 异常信息 | 错误码 |
---|---|---|---|
XuguDB | ZERO_DIVIDE | 除数为0 | 19005 |
XuguDB | NO_DATA_FOUND | 查询无结果 | 19009 |
XuguDB | TOO_MANY_ROWS | 查询结果太多 | 19010 |
XuguDB | DUP_VAL_ON_INDEX | 违反唯一值约束 | 13001 |
- 示例:
sql
SQL> DECLARE
x INT := 2;
y INT := 0;
BEGIN
x := x / y;
EXCEPTION
WHEN ZERO_DIVIDE THEN
SEND_MSG('除数为0');
END;
/
-- 输出
除数为0
SQL> DECLARE
x INT := 2;
y INT := 0;
BEGIN
x := x / y;
EXCEPTION
WHEN 19005 THEN
SEND_MSG('除数为0');
END;
/
-- 输出
除数为0
自定义异常
自定义异常必须在DECLARE
部分使用EXCEPTION
关键字声明,语法参考上文ExceptionDef
。
- 示例:
sql
SQL> CREATE OR REPLACE PROCEDURE pro_1(a INT) IS
DECLARE
e1 EXCEPTION;
e2 EXCEPTION;
BEGIN
IF a > 5 THEN
THROW EXCEPTION e1 ;
ELSE
THROW e2;
END IF;
EXCEPTION
WHEN e1 THEN
send_msg('抛出异常1');
WHEN e2 THEN
send_msg('抛出异常2');
WHEN OTHERS THEN
send_msg('其它抛出异常');
END;
/
SQL> EXECUTE pro_1(2);
抛出异常2
非预定义异常
XuguDB 通过PRAGMA EXCEPTION_INIT
将自定义异常名称与一个特定的错误号关联起来,当在代码中使用RAISE_APPLICATION_ERROR
函数抛出该错误代码时,可以在外层EXCEPTION
块中通过自定义异常名称来捕获它,使代码更清晰。通过关联的错误号捕获异常信息,对于未命名的内部异常则可使用该方式为其添加异常名称,并为其编写一个特定的处理程序,而不是使用OTHERS
处理程序。
- 语法定义
参数说明
name
:当前PL/SQL语句、子程序或包中声明的用户自定义异常名。integer
:错误码。
示例: 自定义异常名与数据库定义错误号E16005关联
sql
SQL> CREATE TABLE tb_ex(id INT PRIMARY KEY);
SQL> DECLARE
no_null EXCEPTION;
PRAGMA EXCEPTION_INIT(no_null,16005);
BEGIN
INSERT INTO tb_ex VALUES(NULL);
EXCEPTION
WHEN no_null THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE);
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/
-- 输出:
16005
字段 ID 不能取空值
- 示例: RAISE_APPLICATION_ERROR 函数自定义错误码
sql
SQL> DECLARE
a NUMERIC(4,0) := NULL;
BEGIN
IF a IS NULL THEN
RAISE_APPLICATION_ERROR(9999, 'A为空');
END IF;
END;
/
-- 输出
Error: [E9999 L5 C1] A为空
异常处理函数
XuguDB 支持的异常处理函数如下表所示:
函数 | 详细信息 |
---|---|
SQLCODE | 详细参考SQLCODE |
SQLERRM | 详细参考SQLERRM |
RAISE_APPLICATION_ERROR | 详细参考RAISE_APPLICATION_ERROR |
DBMS_UTILITY.FORMAT_ERROR_STACK | 详细参考FORMAT_ERROR_STACK |
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE | 详细参考FORMAT_ERROR_BACKTRACE |
引发异常
RAISE
语句用于显式引发一个已声明的异常(通常是用户自定义异常)。
- 示例:
sql
SQL> DECLARE
sal_too_high EXCEPTION;
cur_sal NUMBER := 8000;
max_sal NUMBER := 6000;
err_oneous_sal NUMBER;
BEGIN
BEGIN
IF cur_sal > max_sal THEN
RAISE sal_too_high;
END IF;
EXCEPTION
WHEN sal_too_high THEN -- 开始处理异常
err_oneous_sal := cur_sal;
DBMS_OUTPUT.PUT_LINE ('Salary:' || err_oneous_sal ||' is out of range.');
DBMS_OUTPUT.PUT_LINE ('Maximum salary is ' || max_sal || '.');
RAISE;
END;
EXCEPTION
WHEN sal_too_high THEN -- 最后处理异常
cur_sal := max_sal;
DBMS_OUTPUT.PUT_LINE ('Revising salary from ' || err_oneous_sal ||' to ' || cur_sal || '.');
END;
/
-- 输出
Salary:8000 is out of range.
Maximum salary is 6000.
Revising salary from 8000 to 6000.