PL/SQL:异常处理补充

PL/SQL异常处理补充:本文在基础异常处理语法基础上,重点补充了5个实用知识点:

  1. 常见预定义异常(如NO_DATA_FOUND)及其应用场景;
  2. 两种自定义异常实现方式(异常别名与RAISE_APPLICATION_ERROR);
  3. 异常处理最佳实践,包括日志表设计、带步骤标记的模板和异常处理策略决策树;
  4. 高级技巧如异常传播、批量操作异常隔离和11g+的堆栈追踪;
  5. 推荐的异常处理规范模板。

特别强调事务性操作必须ROLLBACK,并通过日志表记录异常细节,为开发人员提供了从基础到进阶的完整异常处理方案。


PL/SQL:异常处理补充


一、已有知识梳理(保持结构清晰)

1. 核心语法

sql

复制代码
EXCEPTION
  WHEN 异常1 THEN 处理动作1;
  WHEN 异常2 THEN 处理动作2;
  WHEN OTHERS THEN 处理动作;  -- 捕获所有未处理的异常

2. 关键内置函数

函数 说明
SQLERRM 返回完整的异常错误文本
SQLCODE 返回错误代码(数字)

二、重要补充内容

补充1:常见预定义异常(不用声明直接用)

异常名 触发场景 SQLCODE
NO_DATA_FOUND SELECT INTO 未查到数据 +100
TOO_MANY_ROWS SELECT INTO 返回多行 -1422
DUP_VAL_ON_INDEX 违反唯一约束/主键 -1
INVALID_CURSOR 无效游标操作 -1001
ZERO_DIVIDE 除数为0 -1476
INVALID_NUMBER 字符串转数字失败 -1722
LOGIN_DENIED 登录失败 -1017
PROGRAM_ERROR PL/SQL内部错误 -6501

示例:按具体异常分类处理

sql

复制代码
CREATE OR REPLACE PROCEDURE P_EXAMPLE(P_EMPNO NUMBER) IS
  V_ENAME VARCHAR2(100);
BEGIN
  SELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO = P_EMPNO;
  DBMS_OUTPUT.PUT_LINE(V_ENAME);
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('错误:员工编号' || P_EMPNO || '不存在');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('错误:数据重复,查出多行');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('未知错误:' || SQLERRM);
    RAISE;  -- 重新抛出异常
END;

补充2:自定义异常(业务逻辑异常)

方式一:预定义异常别名

sql

复制代码
DECLARE
  E_SAL_TOO_HIGH EXCEPTION;
  PRAGMA EXCEPTION_INIT(E_SAL_TOO_HIGH, -20001);  -- 绑定自定义错误号
BEGIN
  IF V_SAL > 50000 THEN
    RAISE E_SAL_TOO_HIGH;
  END IF;
EXCEPTION
  WHEN E_SAL_TOO_HIGH THEN
    DBMS_OUTPUT.PUT_LINE('薪资超过限额');
END;

方式二:使用RAISE_APPLICATION_ERROR(最常用)

sql

复制代码
CREATE OR REPLACE PROCEDURE P_UPDATE_SAL(P_EMPNO NUMBER, P_NEW_SAL NUMBER) IS
BEGIN
  IF P_NEW_SAL < 0 THEN
    RAISE_APPLICATION_ERROR(-20001, '薪资不能为负数');
  END IF;
  IF P_NEW_SAL > 50000 THEN
    RAISE_APPLICATION_ERROR(-20002, '薪资不能超过50000');
  END IF;
  UPDATE EMP SET SAL = P_NEW_SAL WHERE EMPNO = P_EMPNO;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    -- 写入日志表
    INSERT INTO ERROR_LOG(ERR_DATE, ERR_CODE, ERR_MSG, PROC_NAME)
    VALUES(SYSDATE, SQLCODE, SQLERRM, 'P_UPDATE_SAL');
    COMMIT;
    RAISE;  -- 继续向上抛出
END;

注意 :RAISE_APPLICATION_ERROR 的错误号范围:-20000-20999


补充3:异常处理的最佳实践(工作必备)

① 日志表示例

sql

复制代码
CREATE TABLE ERROR_LOG (
  LOG_ID     NUMBER PRIMARY KEY,
  PROC_NAME  VARCHAR2(100),
  ERR_CODE   NUMBER,
  ERR_MSG    VARCHAR2(4000),
  ERR_DATE   DATE,
  USER_NAME  VARCHAR2(30)
);

CREATE SEQUENCE SEQ_ERROR_LOG;
② 带日志的存储过程模板

sql

复制代码
CREATE OR REPLACE PROCEDURE P_BUSINESS(P_ID NUMBER) IS
  V_STEP VARCHAR2(100);
BEGIN
  V_STEP := '查询用户信息';
  -- 业务逻辑1
  
  V_STEP := '更新订单状态';
  -- 业务逻辑2
  
  V_STEP := '发送通知';
  -- 业务逻辑3
  
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    INSERT INTO ERROR_LOG(LOG_ID, PROC_NAME, ERR_CODE, ERR_MSG, ERR_DATE, USER_NAME)
    VALUES(SEQ_ERROR_LOG.NEXTVAL, 'P_BUSINESS', SQLCODE, SQLERRM, SYSDATE, USER);
    COMMIT;  -- 日志独立提交
    
    -- 根据业务决定是否继续抛出
    RAISE_APPLICATION_ERROR(-20099, '程序执行失败,失败步骤:' || V_STEP || ',原因:' || SQLERRM);
END;
③ 异常处理策略决策树

text

复制代码
发生异常
   │
   ├── 可预期且可恢复(如NO_DATA_FOUND)
   │    └── 捕获后给默认值或跳过,继续执行
   │
   ├── 可预期但不可恢复(如参数校验失败)
   │    └── RAISE_APPLICATION_ERROR 返回友好错误信息
   │
   ├── 不可预期异常
   │    └── OTHERS捕获 → 写日志 → 回滚 → RAISE抛出
   │
   └── 事务性操作(多个DML)
        └── 异常必须ROLLBACK,保证数据一致性

补充4:高级异常处理技巧

① 异常传播机制

sql

复制代码
BEGIN
  子程序调用  -- 子程序内异常若未捕获,会传播到外层
EXCEPTION
  WHEN OTHERS THEN
    -- 外层可以再处理
END;
② 批量操作中的异常处理(常用)

sql

复制代码
DECLARE
  TYPE T_EMP_ID IS TABLE OF EMP.EMPNO%TYPE;
  V_IDS T_EMP_ID := T_EMP_ID(7369, 7499, 9999, 7521);
BEGIN
  FOR I IN 1..V_IDS.COUNT LOOP
    BEGIN  -- 内层块隔离异常
      UPDATE EMP SET SAL = SAL * 1.1 WHERE EMPNO = V_IDS(I);
      IF SQL%NOTFOUND THEN
        RAISE_APPLICATION_ERROR(-20001, '员工不存在:' || V_IDS(I));
      END IF;
    EXCEPTION
      WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('处理员工' || V_IDS(I) || '失败:' || SQLERRM);
        -- 继续处理下一个,不影响事务一致性
    END;
  END LOOP;
  COMMIT;
END;
③ 获取异常完整堆栈(11g+)

sql

复制代码
DECLARE
  V_STACK CLOB;
BEGIN
  -- 业务代码
  RAISE ZERO_DIVIDE;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_UTILITY.FORMAT_ERROR_STACK();   -- 错误信息
    DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(); -- 调用堆栈(行号)
    DBMS_UTILITY.FORMAT_CALL_STACK();      -- 完整调用栈
END;

补充5:RAISE 的使用对比

写法 含义
RAISE; 重新抛出当前异常,保留原始错误信息
RAISE NO_DATA_FOUND; 抛出指定的预定义异常
RAISE_APPLICATION_ERROR(code, msg); 抛出自定义错误(不进入OTHERS?------会进入)

三、工作中的异常处理规范(推荐)

sql

复制代码
CREATE OR REPLACE PROCEDURE 存储过程名(参数) IS
  -- 声明变量
  V_ERR_MSG VARCHAR2(4000);
BEGIN
  -- 1. 参数校验(主动抛异常)
  IF 参数不合法 THEN
    RAISE_APPLICATION_ERROR(-20001, '参数错误:...');
  END IF;
  
  -- 2. 业务处理(每一段关键操作后COMMIT或统一提交)
  
  -- 3. 正常提交
  COMMIT;
EXCEPTION
  WHEN DUP_VAL_ON_INDEX THEN
    ROLLBACK;
    RAISE_APPLICATION_ERROR(-20002, '数据重复');
  WHEN OTHERS THEN
    ROLLBACK;
    V_ERR_MSG := SQLERRM;
    -- 写日志
    INSERT INTO ERROR_LOG VALUES(...);
    COMMIT;
    -- 重新抛出给调用方
    RAISE;
END;

四、总结对比表

维度 原笔记 补充说明
语法 WHEN OTHERS THEN ... 还可以按具体异常分类处理
异常信息 SQLERRM 还有 SQLCODEDBMS_UTILITY.FORMAT_ERROR_BACKTRACE
异常后行为 继续/回滚 可以通过 RAISE 中断并向上传播
自定义异常 未涉及 RAISE_APPLICATION_ERROR 是最常用方式
日志记录 提到但未展开 工作中写日志表是关键实践
批量场景 未涉及 内层块独立异常处理,让循环继续

如果您需要,我可以进一步提供:

  1. 异常日志表的完整建表语句和插入模板

  2. Spring Boot 调用 Oracle 存储过程时如何接收自定义异常

  3. 异常处理与事务边界的设计模式

相关推荐
dishugj1 小时前
SAP HANA数据库文件目录说明
服务器·数据库·oracle
l1t1 小时前
DeepSeek总结的使用 eBPF 和硬件断点跟踪 PostgreSQL
数据库·驱动开发·postgresql
薪火铺子1 小时前
MySQL InnoDB 索引底层:B+树深度解析
数据库·b树·mysql
Elastic 中国社区官方博客1 小时前
从平均值到任意百分位数:Elasticsearch 在 ES|QL 中原生支持指数直方图
大数据·数据库·sql·elasticsearch·搜索引擎·全文检索·prometheus
今儿敲了吗2 小时前
数据库(四)——关系数据库SQL语言
数据库·笔记·sql
麦聪聊数据2 小时前
SQL与数据库开发(二):用 CTE 重构嵌套查询与处理树形结构
sql·数据库开发
brevity_souls2 小时前
SQL server格式化日期
运维·服务器·数据库
虹科网络安全2 小时前
艾体宝干货|Active-Active/Active-Passive 数据库架构解析:高可用设计中的权衡与选型
数据库·数据库架构
麦聪聊数据2 小时前
SQL与数据库开发(一):用窗口函数替代应用层的嵌套循环
数据库·sql·数据库开发