需先声明异常变量并用PRAGMA EXCEPTION_INIT绑定ORA错误码,如e_uk_violation EXCEPTION; PRAGMA EXCEPTION_INIT(e_uk_violation, -1);再在EXCEPTION块中WHEN e_uk_violation THEN处理;不可仅用WHEN OTHERS THEN RAISE,否则丢失堆栈、行号及绑定变量,且绕过PRAGMA绑定;自定义异常应含业务语义(如“[薪资计算]员工ID12345薪资超上限”),并通过自治事务写入日志表确保可追溯;SQLCODE/SQLERRM仅在当前EXCEPTION块有效,日志函数须显式传入而非内部获取。

如何声明并关联非预定义异常(ORA-错误码)
Oracle不会自动给所有系统级错误分配命名异常,比如 ORA-00001(唯一约束违例)或 ORA-02291(外键违例)就属于“非预定义异常”。这类错误必须手动声明异常变量,并用 PRAGMA EXCEPTION_INIT 绑定错误码,否则 WHEN DUP_VAL_ON_INDEX THEN 这类写法根本捕获不到它们。
常见错误写法是直接在 EXCEPTION 块里写 WHEN OTHERS THEN,结果掩盖了真实错误类型,调试时只能靠 SQLCODE 硬查。
- 先在声明区定义异常变量:
e_uk_violation EXCEPTION; - 紧接其后绑定错误码:
PRAGMA EXCEPTION_INIT(e_uk_violation, -1); - 在
EXCEPTION块中单独处理:WHEN e_uk_violation THEN RAISE_APPLICATION_ERROR(-20001, '插入重复主键'); - 注意:错误码带负号,
-1对应ORA-00001,-2291对应外键违例
为什么不能只靠 WHEN OTHERS THEN RAISE
WHEN OTHERS THEN RAISE 看似“重新抛出”,实则丢失关键上下文:原异常的堆栈、行号、绑定变量值全不可见;更严重的是,它会绕过 PRAGMA EXCEPTION_INIT 的绑定逻辑,导致你精心定义的 e_uk_violation 形同虚设。
实际执行中,一旦进入 WHEN OTHERS 分支,你就只能靠 SQLERRM 拼字符串判断类型,既脆弱又低效。
-
SQLERRM可能被截断(默认 512 字节),ORA-01403: no data found后面若跟长 SQL,关键信息就丢了 -
SQLCODE在WHEN OTHERS外不可访问,想记录日志必须在该分支内立刻取值 - 嵌套调用时,
RAISE不会保留原始调用栈,上层过程看到的只是“未命名异常”
如何让自定义异常带业务语义并可追溯
纯靠 RAISE_APPLICATION_ERROR 抛出数字错误码,对运维和下游调用方都不友好。真正可维护的做法是:把业务含义塞进错误消息,同时用自治事务写入日志表,确保即使主事务回滚,错误记录也不丢。
关键点不在“抛”,而在“抛得清楚”——谁在什么节点、因什么业务规则失败。
- 错误消息开头加模块标记,例如:
'[薪资计算] 员工ID 12345 薪资超上限' - 调用自治事务日志过程:
p_messagelogging('E', 'p_calc_salary', v_errmsg); - 日志表字段要包含
log_node(过程名)、log_s_no(序列号)、log_date(精确到秒) - 避免在日志中拼接敏感字段(如身份证号),用脱敏占位符代替
容易被忽略的 SQLCODE 作用域陷阱
SQLCODE 和 SQLERRM 只在当前 EXCEPTION 块内有效,且每次异常发生后才刷新。如果在 WHEN OTHERS 里调用了一个函数,而该函数内部又触发异常,那么外层的 SQLCODE 就变成那个函数里的值,不是原始错误。
这意味着:任何中间封装的日志函数,都必须把 SQLCODE 和 SQLERRM 当作参数传进去,不能在函数内部现场取值。
- 错误示范:
p_log_error('步骤X失败');—— 函数内再取SQLCODE已失真 - 正确做法:
p_log_error('步骤X失败', SQLCODE, SQLERRM); - 尤其注意:在
SAVEPOINT+ 局部回滚后,SQLCODE仍指向最初异常,不会变











