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_FOUNDORA-01403无数据
TOO_MANY_ROWSORA-01422多行
ZERO_DIVIDEORA-01476除零
INVALID_CURSORORA-01001无效游标
VALUE_ERRORORA-06502值错误
DUP_VAL_ON_INDEXORA-00001唯一约束冲突
INVALID_NUMBERORA-01722无效数字
CURSOR_ALREADY_OPENORA-06511游标已打开
LOGIN_DENIEDORA-01017登录失败
NOT_LOGGED_ONORA-01012未登录
PROGRAM_ERRORORA-06501程序错误
STORAGE_ERRORORA-06500存储错误
TIMEOUT_ON_RESOURCEORA-00051资源超时
TRANSACTION_BACKED_OUTORA-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. 最佳实践

  1. 始终处理异常:避免崩溃
  2. 具体异常优先:精准处理
  3. WHEN OTHERS 兜底:防止遗漏
  4. 记录错误:便于排查
  5. DBMS_UTILITY.FORMAT_ERROR_BACKTRACE:定位错误
  6. RAISE 传播:让上层处理
  7. 自治事务记日志:保证日志保留
  8. 避免吞掉错误:NULL 隐藏问题
  9. 业务异常用 RAISE_APPLICATION_ERROR:自定义错误
  10. 测试异常路径:健壮性

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