Oracle 异常处理(Exception Handling)
Oracle 异常处理(Exception Handling)
适用版本:Oracle Database 8i / 9i / 10g / 11g / 12c / 19c / 23ai 文档版本:v1.0 / 2026-07
1. 概述
异常处理 用于处理运行时错误[1]:
核心优势:
- 程序健壮性
- 错误集中处理
- 业务逻辑清晰
- 资源释放
2. 异常结构
DECLARE
...
BEGIN
...
EXCEPTION
WHEN exception1 THEN
-- 处理异常 1
WHEN exception2 THEN
-- 处理异常 2
WHEN OTHERS THEN
-- 处理其他异常
END;
3. 预定义异常
3.1 常用预定义异常
| 异常 | 错误码 | 说明 |
|---|---|---|
| NO_DATA_FOUND | ORA-01403 | 无数据 |
| TOO_MANY_ROWS | ORA-01422 | 多行 |
| ZERO_DIVIDE | ORA-01476 | 除零 |
| INVALID_CURSOR | ORA-01001 | 无效游标 |
| VALUE_ERROR | ORA-06502 | 值错误 |
| DUP_VAL_ON_INDEX | ORA-00001 | 唯一约束冲突 |
| INVALID_NUMBER | ORA-01722 | 无效数字 |
| CURSOR_ALREADY_OPEN | ORA-06511 | 游标已打开 |
| LOGIN_DENIED | ORA-01017 | 登录失败 |
| NOT_LOGGED_ON | ORA-01012 | 未登录 |
| PROGRAM_ERROR | ORA-06501 | 程序错误 |
| STORAGE_ERROR | ORA-06500 | 存储错误 |
| TIMEOUT_ON_RESOURCE | ORA-00051 | 资源超时 |
| TRANSACTION_BACKED_OUT | ORA-00060 | 死锁 |
3.2 示例
DECLARE
v_name VARCHAR2(100);
BEGIN
SELECT last_name INTO v_name
FROM employees
WHERE employee_id = 999;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Employee not found');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Multiple employees found');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
4. 非预定义异常
4.1 关联错误码
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('Cannot delete: child records exist');
END;
5. 自定义异常
5.1 声明与抛出
DECLARE
e_invalid_salary EXCEPTION;
v_salary NUMBER := -100;
BEGIN
IF v_salary < 0 THEN
RAISE e_invalid_salary;
END IF;
EXCEPTION
WHEN e_invalid_salary THEN
DBMS_OUTPUT.PUT_LINE('Salary cannot be negative');
END;
5.2 关联错误码
DECLARE
e_invalid_salary EXCEPTION;
PRAGMA EXCEPTION_INIT(e_invalid_salary, -20001);
v_salary NUMBER := -100;
BEGIN
IF v_salary < 0 THEN
RAISE e_invalid_salary;
END IF;
EXCEPTION
WHEN e_invalid_salary THEN
DBMS_OUTPUT.PUT_LINE('Error 20001: Invalid salary');
END;
6. RAISE_APPLICATION_ERROR
6.1 语法
RAISE_APPLICATION_ERROR(
error_number, -- -20000 到 -20999
error_message,
[keep_errors] -- TRUE/FALSE
);
6.2 示例
CREATE OR REPLACE PROCEDURE update_salary(
p_emp_id NUMBER,
p_salary NUMBER
) AS
BEGIN
IF p_salary < 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'Salary cannot be negative');
END IF;
IF p_salary > 100000 THEN
RAISE_APPLICATION_ERROR(-20002, 'Salary exceeds maximum: 100000');
END IF;
UPDATE employees SET salary = p_salary WHERE employee_id = p_emp_id;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20003, 'Employee not found: ' || p_emp_id);
END IF;
END;
/
7. 错误函数
7.1 SQLCODE 和 SQLERRM
DECLARE
v_code NUMBER;
v_msg VARCHAR2(1000);
BEGIN
...
EXCEPTION
WHEN OTHERS THEN
v_code := SQLCODE;
v_msg := SQLERRM;
DBMS_OUTPUT.PUT_LINE('Error ' || v_code || ': ' || v_msg);
INSERT INTO error_log (error_code, error_msg, error_date)
VALUES (v_code, v_msg, SYSDATE);
END;
7.2 DBMS_UTILITY.FORMAT_ERROR_STACK
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_STACK);
7.3 DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
-- 显示错误发生位置
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
-- 输出:
-- ORA-06512: at "SCOTT.UPDATE_SALARY", line 5
-- ORA-06512: at line 2
8. 异常传播
8.1 嵌套块
BEGIN
BEGIN
-- 内层块
RAISE NO_DATA_FOUND;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Inner: No data');
-- 不再 RAISE,外层不感知
END;
-- 外层继续执行
DBMS_OUTPUT.PUT_LINE('Outer continues');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Outer error');
END;
8.2 RAISE 重新抛出
BEGIN
BEGIN
RAISE NO_DATA_FOUND;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Logging...');
RAISE; -- 重新抛出,外层处理
END;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Outer handles');
END;
9. 异常处理模式
9.1 记录并继续
BEGIN
FOR rec IN cur LOOP
BEGIN
-- 可能出错的操作
UPDATE ...;
EXCEPTION
WHEN OTHERS THEN
INSERT INTO error_log VALUES (rec.id, SQLERRM, SYSDATE);
END;
END LOOP;
END;
9.2 记录并抛出
EXCEPTION
WHEN OTHERS THEN
INSERT INTO error_log VALUES (SQLCODE, SQLERRM, SYSDATE);
RAISE;
END;
9.3 转换异常
DECLARE
e_custom EXCEPTION;
BEGIN
BEGIN
SELECT ... INTO ... FROM ...;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE e_custom;
END;
EXCEPTION
WHEN e_custom THEN
DBMS_OUTPUT.PUT_LINE('Custom handling');
END;
10. 自治事务
CREATE OR REPLACE PROCEDURE log_error(
p_code NUMBER,
p_msg VARCHAR2
) AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO error_log VALUES (p_code, p_msg, SYSDATE);
COMMIT; -- 自治事务必须 COMMIT/ROLLBACK
END;
/
-- 在异常中调用
EXCEPTION
WHEN OTHERS THEN
log_error(SQLCODE, SQLERRM); -- 即使主事务回滚,日志保留
RAISE;
END;
11. 常见坑与排错
11.1 异常未处理
-- 未处理的异常会传播到调用方
-- 始终使用 WHEN OTHERS 兜底
11.2 WHEN OTHERS 隐藏错误
-- 不推荐
EXCEPTION
WHEN OTHERS THEN
NULL; -- 吞掉错误
-- 推荐
EXCEPTION
WHEN OTHERS THEN
log_error(SQLCODE, SQLERRM);
RAISE;
11.3 SQLCODE 在 EXCEPTION 之外
-- SQLCODE 仅在异常处理块中有效
-- 在其他地方返回 0
11.4 异常处理顺序
-- 顺序:具体异常 → OTHERS
EXCEPTION
WHEN NO_DATA_FOUND THEN ... -- 具体异常
WHEN OTHERS THEN ... -- 必须最后
12. 最佳实践
- 始终处理异常:避免崩溃
- 具体异常优先:精准处理
- WHEN OTHERS 兜底:防止遗漏
- 记录错误:便于排查
- DBMS_UTILITY.FORMAT_ERROR_BACKTRACE:定位错误
- RAISE 传播:让上层处理
- 自治事务记日志:保证日志保留
- 避免吞掉错误:NULL 隐藏问题
- 业务异常用 RAISE_APPLICATION_ERROR:自定义错误
- 测试异常路径:健壮性
13. 参考资料
[1] Oracle Database PL/SQL Language Reference 19c, “Exception Handling” https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/exception-handling.html