Oracle PL/SQL 异常处理详解
Oracle PL/SQL 异常处理详解
适用版本:Oracle Database 10g / 11g / 12c / 19c / 23ai 文档版本:v1.0 / 2026-07
1. 概述
PL/SQL 异常处理保证程序健壮性[1]:
详细见:Oracle PL/SQL 基础与块结构。
2. 异常类型
2.1 预定义
DECLARE
v_name employees.name%TYPE;
BEGIN
SELECT name INTO v_name FROM employees WHERE id = 999;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Not found');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Too many');
END;
/
2.2 常用预定义
| 异常 | 错误码 | 描述 |
|---|---|---|
| NO_DATA_FOUND | ORA-01403 | 无数据 |
| TOO_MANY_ROWS | ORA-01422 | 多行 |
| ZERO_DIVIDE | ORA-01476 | 除零 |
| INVALID_CURSOR | ORA-01001 | 无效游标 |
| INVALID_NUMBER | ORA-01722 | 无效数字 |
| VALUE_ERROR | ORA-06502 | 值错误 |
| DUP_VAL_ON_INDEX | ORA-00001 | 唯一约束 |
| TIMEOUT_ON_RESOURCE | ORA-00051 | 超时 |
| LOGIN_DENIED | ORA-01017 | 登录失败 |
| NOT_LOGGED_ON | ORA-01012 | 未登录 |
2.3 非预定义
DECLARE
e_fk_violation EXCEPTION;
PRAGMA EXCEPTION_INIT(e_fk_violation, -02292);
BEGIN
DELETE FROM departments WHERE id = 10;
EXCEPTION
WHEN e_fk_violation THEN
DBMS_OUTPUT.PUT_LINE('FK violation');
END;
/
2.4 自定义
DECLARE
e_salary_too_high EXCEPTION;
v_max_salary NUMBER := 100000;
BEGIN
IF :new_salary > v_max_salary THEN
RAISE e_salary_too_high;
END IF;
EXCEPTION
WHEN e_salary_too_high THEN
DBMS_OUTPUT.PUT_LINE('Salary too high');
END;
/
3. RAISE
3.1 RAISE 异常
DECLARE
e_custom EXCEPTION;
BEGIN
IF condition THEN
RAISE e_custom;
END IF;
EXCEPTION
WHEN e_custom THEN
DBMS_OUTPUT.PUT_LINE('Custom error');
END;
/
3.2 RAISE_APPLICATION_ERROR
CREATE OR REPLACE PROCEDURE hire_emp(p_salary NUMBER) IS
BEGIN
IF p_salary < 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'Salary cannot be negative');
ELSIF p_salary > 100000 THEN
RAISE_APPLICATION_ERROR(-20002, 'Salary too high: ' || p_salary, TRUE);
END IF;
END;
/
3.3 RAISE 重新抛出
BEGIN
BEGIN
-- 操作
EXCEPTION
WHEN OTHERS THEN
log_error();
RAISE; -- 重新抛出
END;
EXCEPTION
WHEN OTHERS THEN
-- 外层处理
END;
/
4. EXCEPTION 块
4.1 结构
DECLARE
...
BEGIN
...
EXCEPTION
WHEN e1 THEN
-- 处理 1
WHEN e2 OR e3 THEN
-- 处理 2/3
WHEN OTHERS THEN
-- 其他
END;
/
4.2 SQLCODE / SQLERRM
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error: ' || SQLCODE);
DBMS_OUTPUT.PUT_LINE('Message: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('Stack: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
DBMS_OUTPUT.PUT_LINE('Stack: ' || DBMS_UTILITY.FORMAT_ERROR_STACK);
DBMS_OUTPUT.PUT_LINE('Call: ' || DBMS_UTILITY.FORMAT_CALL_STACK);
5. 错误信息
5.1 SQLCODE
- 错误码
- 0:成功
- 1:自定义(无 EXCEPTION_INIT)
- 负数:Oracle
- +100:NO_DATA_FOUND
5.2 SQLERRM
- 错误消息
- SQLERRM:当前
- SQLERRM(-20001):指定
5.3 DBMS_UTILITY
FORMAT_ERROR_BACKTRACE - 调用栈(位置)
FORMAT_ERROR_STACK - 错误栈
FORMAT_CALL_STACK - 调用栈
6. 异常传播
6.1 内部块
BEGIN
BEGIN
RAISE e_custom; -- 抛出
EXCEPTION
WHEN OTHERS THEN
-- 处理
-- 不再传播
END;
-- 继续执行
END;
6.2 向上传播
BEGIN
BEGIN
RAISE e_custom; -- 抛出
-- 无 EXCEPTION 处理
END;
-- 不到这里
EXCEPTION
WHEN e_custom THEN
-- 外层处理
END;
7. 自定义异常
7.1 基本
CREATE OR REPLACE PACKAGE emp_errors AS
e_salary_invalid EXCEPTION;
PRAGMA EXCEPTION_INIT(e_salary_invalid, -20001);
e_dept_not_found EXCEPTION;
PRAGMA EXCEPTION_INIT(e_dept_not_found, -20002);
END;
/
CREATE OR REPLACE PROCEDURE hire_emp(p_salary NUMBER) IS
BEGIN
IF p_salary <= 0 THEN
RAISE emp_errors.e_salary_invalid;
END IF;
EXCEPTION
WHEN emp_errors.e_salary_invalid THEN
DBMS_OUTPUT.PUT_LINE('Salary invalid');
END;
/
7.2 RAISE_APPLICATION_ERROR
CREATE OR REPLACE PROCEDURE hire_emp(p_salary NUMBER) IS
BEGIN
IF p_salary <= 0 THEN
RAISE_APPLICATION_ERROR(
-20001,
'Salary must be positive, got: ' || p_salary,
TRUE -- 保留错误栈
);
END IF;
END;
/
8. 异常函数
8.1 SQLERRM
-- 当前
DBMS_OUTPUT.PUT_LINE(SQLERRM);
-- 指定
DBMS_OUTPUT.PUT_LINE(SQLERRM(-20001));
8.2 DBMS_UTILITY
DECLARE
v_line VARCHAR2(4000);
BEGIN
v_line := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
DBMS_OUTPUT.PUT_LINE(v_line);
END;
/
9. 异常日志
9.1 日志表
CREATE TABLE error_log (
id NUMBER GENERATED ALWAYS AS IDENTITY,
procedure_name VARCHAR2(100),
error_code NUMBER,
error_message VARCHAR2(4000),
error_backtrace CLOB,
log_time TIMESTAMP
);
CREATE OR REPLACE PROCEDURE log_error(p_proc VARCHAR2) IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO error_log (procedure_name, error_code, error_message, error_backtrace, log_time)
VALUES (
p_proc,
SQLCODE,
SQLERRM,
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,
SYSTIMESTAMP
);
COMMIT;
END;
/
9.2 使用
CREATE OR REPLACE PROCEDURE my_proc IS
BEGIN
-- 业务逻辑
...
EXCEPTION
WHEN OTHERS THEN
log_error('MY_PROC');
RAISE;
END;
/
10. 异常处理模式
10.1 单个处理
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- 处理
END;
10.2 多个组合
EXCEPTION
WHEN NO_DATA_FOUND OR TOO_MANY_ROWS THEN
-- 查询异常
WHEN OTHERS THEN
-- 其他
END;
10.3 嵌套
BEGIN
BEGIN
-- 操作
EXCEPTION
WHEN OTHERS THEN
-- 内层
RAISE;
END;
EXCEPTION
WHEN OTHERS THEN
-- 外层
END;
11. 常见异常场景
11.1 查询
BEGIN
SELECT ... INTO ... FROM ...;
EXCEPTION
WHEN NO_DATA_FOUND THEN ...
WHEN TOO_MANY_ROWS THEN ...
END;
11.2 DML
BEGIN
INSERT INTO ...;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN ...
WHEN OTHERS THEN ...
END;
11.3 自定义
BEGIN
IF condition THEN
RAISE_APPLICATION_ERROR(-20001, '...');
END IF;
EXCEPTION
WHEN OTHERS THEN ...
END;
11.4 BULK
BEGIN
FORALL i IN 1..v_ids.COUNT SAVE EXCEPTIONS
INSERT INTO t VALUES (v_ids(i));
EXCEPTION
WHEN OTHERS THEN
FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(
'Index ' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||
' Code ' || SQL%BULK_EXCEPTIONS(i).ERROR_CODE
);
END LOOP;
END;
详细见:Oracle BULK COLLECT 与 FORALL。
12. 最佳实践
12.1 具体
- 避免单一 WHEN OTHERS
- 具体异常具体处理
12.2 日志
- 错误日志
- 自治事务
- 完整信息
12.3 传播
- 内层处理后传播
- 外层最终处理
- 不丢失信息
12.4 用户友好
- 业务消息
- 技术细节日志
- 用户清晰
13. 常见坑与排错
13.1 隐藏错误
- WHEN OTHERS THEN NULL
- 避免
- 至少记录
13.2 异常丢失
- 不传播
- 外层不知
- RAISE
13.3 死循环
- 异常处理引发异常
- 谨慎
14. 最佳实践
- 具体异常:明确
- WHEN OTHERS 谨慎:兜底
- 日志:完整
- RAISE 传播:不丢失
- RAISE_APPLICATION_ERROR:自定义
- PRAGMA EXCEPTION_INIT:关联
- DBMS_UTILITY:栈
- BULK:SAVE EXCEPTIONS
- 用户友好:消息
- 测试:验证
15. 参考资料
[1] Oracle Database PL/SQL Language Reference 19c, “Exceptions” https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/plsql-error-handling.html