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_FOUNDORA-01403无数据
TOO_MANY_ROWSORA-01422多行
ZERO_DIVIDEORA-01476除零
INVALID_CURSORORA-01001无效游标
INVALID_NUMBERORA-01722无效数字
VALUE_ERRORORA-06502值错误
DUP_VAL_ON_INDEXORA-00001唯一约束
TIMEOUT_ON_RESOURCEORA-00051超时
LOGIN_DENIEDORA-01017登录失败
NOT_LOGGED_ONORA-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. 最佳实践

  1. 具体异常:明确
  2. WHEN OTHERS 谨慎:兜底
  3. 日志:完整
  4. RAISE 传播:不丢失
  5. RAISE_APPLICATION_ERROR:自定义
  6. PRAGMA EXCEPTION_INIT:关联
  7. DBMS_UTILITY:栈
  8. BULK:SAVE EXCEPTIONS
  9. 用户友好:消息
  10. 测试:验证

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