Oracle PL/SQL优雅异常处理的核心是:用PRAGMA EXCEPTION_INIT绑定ORA错误号到命名异常,再用RAISE_APPLICATION_ERROR抛出自定义业务错误;前者实现精准捕获,后者传递语义化信息。
oracle pl/sql 里真正“优雅”的异常处理,不是靠 when others 吞掉所有错误,而是让每个业务异常有名字、有编号、有上下文。核心就两条:用 pragma exception_init 绑定已知错误号,用 raise_application_error 抛出自定义语义错误。
怎么把 ORA 错误号映射成可读的异常名
当 Oracle 内部报错(比如唯一约束冲突 ORA-00001),它默认不带名字,你不能直接写 WHEN DUP_VAL_ON_INDEX —— 那是预定义异常,只覆盖 24 个常见情况。其他错误号必须手动绑定。
关键点在于:PRAGMA EXCEPTION_INIT 是编译期指令,不是运行时函数;它必须出现在声明段,且异常变量得先声明。
- 声明顺序不能错:先
my_dup_exception EXCEPTION;,再PRAGMA EXCEPTION_INIT(my_dup_exception, -1); - 错误号必须带负号:
-1正确,1或ORA-00001都会编译失败 - 一个异常名只能绑定一个错误号;想捕获多个错误?得声明多个异常变量
- 绑定后,
WHEN my_dup_exception就能精准拦截,不会误伤ORA-02291(外键违例)这类相似错误
示例:
DECLARE
e_unique_violation EXCEPTION;
PRAGMA EXCEPTION_INIT(e_unique_violation, -1);
BEGIN
INSERT INTO users (id, name) VALUES (1, 'Alice');
EXCEPTION
WHEN e_unique_violation THEN
DBMS_OUTPUT.PUT_LINE('用户ID已存在,拒绝重复注册');
END;
什么时候该用 RAISE_APPLICATION_ERROR 而不是 raise
raise 只能抛出已声明的异常变量(包括预定义和 PRAGMA 绑定的),但无法带业务语义。真正需要向调用方传递“为什么失败”的场景,必须用 RAISE_APPLICATION_ERROR。
它生成的是 ORA-20xxx 级别错误,会被上层应用(如 Java 的 JDBC)原样捕获,而不是被 PL/SQL 层吃掉。
- 错误号必须在
-20000到-20999之间,超出范围会报PLS-00304 - 第三个参数控制错误栈行为:
TRUE表示追加到现有错误栈(适合链式日志),FALSE(默认)会覆盖前序错误 - 不要在
WHEN OTHERS里无条件RAISE_APPLICATION_ERROR—— 这会丢失原始SQLCODE和堆栈,调试时只剩一句“未知错误” - 建议搭配
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE记录完整调用路径
示例(校验失败时主动报错):
IF v_age < 0 THEN RAISE_APPLICATION_ERROR(-20001, '年龄不能为负数', TRUE); END IF;
自定义异常 + PRAGMA 的典型误用坑
很多人以为只要写了 PRAGMA 就能捕获任意错误,结果发现根本进不了 WHEN 分支 —— 常见原因就几个:
-
PRAGMA放在BEGIN后面:编译直接报PLS-00113,必须在声明段 - 异常变量没声明就写
PRAGMA EXCEPTION_INIT(xxx, -1):报PLS-00201(必须声明标识符) - 试图用同一个异常名绑定两个错误号:语法允许,但运行时只生效最后一个
PRAGMA,前面的被覆盖 - 在包体(package body)里声明异常并用
PRAGMA,但包规范(spec)没导出该异常名:调用方无法识别,WHEN不生效 -
RAISE_APPLICATION_ERROR抛出的-20001,却用PRAGMA EXCEPTION_INIT(e_xxx, -1)去捕获:错误号不匹配,必然漏掉
最易被忽略的一点:自定义异常名本身不携带任何错误上下文,SQLCODE 和 SQLERRM 在 WHEN 块里返回的仍是底层 Oracle 错误值,不是你 RAISE_APPLICATION_ERROR 传进去的字符串。真要拿到自定义消息,得靠应用层解析 SQLERRM 内容,或者改用 DBMS_UTILITY.FORMAT_ERROR_STACK 提取原始错误链。


















