WHEN OTHERS 必须首行调用 DBMS_UTILITY.FORMAT_ERROR_BACKTRACE 获取带行号的完整调用链,不可仅用 SQLERRM 或 FORMAT_ERROR_STACK;12c+ 应优先封装 UTL_CALL_STACK;捕获非预定义错误需配合 PRAGMA EXCEPTION_INIT。

WHEN OTHERS 必须搭配 FORMAT_ERROR_BACKTRACE,不能只用 SQLERRM
只写 SQLERRM 会丢失关键定位信息:它只返回类似 ORA-00001: unique constraint (SCOTT.PK_TEST) violated 这种笼统提示,完全看不到出错在哪个包、哪个过程、哪一行。真正排障靠的是 DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,它输出带行号的完整调用链,例如:
ORA-06512: at "SCOTT.PROC_INSERT", line 2 ORA-06512: at "SCOTT.PKG_UTILS", line 45 ORA-06512: at line 1
注意三点:
-
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE必须放在WHEN OTHERS的第一行,不能先做日志写入、事务回滚或变量赋值——任何前置操作都可能污染堆栈 - 不要在调用它之后再写
RAISE;RAISE会重置堆栈,导致返回的是RAISE所在行,而非原始错误点 - 记录日志时建议拼接:
SQLERRM || CHR(10) || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,缺一不可
区分 FORMAT_ERROR_BACKTRACE 和 FORMAT_ERROR_STACK
这两个函数名字像、常被混用,但作用完全不同:
-
DBMS_UTILITY.FORMAT_ERROR_STACK等价于SQLERRM,只返回错误类型和简短消息(如ORA-00001: ...) -
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE才是真正的堆栈跟踪,含对象名、行号、调用顺序 - 仅用
FORMAT_ERROR_STACK记录日志,等于没记——你根本没法定位问题源头
典型误用场景:在日志表中只存了 FORMAT_ERROR_STACK 输出,结果线上报错后只能看到“违反唯一约束”,却找不到是哪个存储过程、哪条 INSERT 语句触发的。
Oracle 12c+ 推荐改用 UTL_CALL_STACK
UTL_CALL_STACK 是 Oracle 12c 引入的结构化替代方案,比 DBMS_UTILITY 函数更可控、更稳定:
- 支持逐层读取:用
UTL_CALL_STACK.DYNAMIC_DEPTH获取当前调用深度,再循环调用UTL_CALL_STACK.UNIT_LINE、UTL_CALL_STACK.OWNER等函数提取每层的包名、过程名、行号 - 可跳过匿名块干扰项:通过
UTL_CALL_STACK.CONTEXT判断调用类型,过滤掉无意义的 PL/SQL 匿名块层级 - 不会因异常传播被意外截断,稳定性优于
FORMAT_ERROR_BACKTRACE
如果你的数据库版本 ≥ 12c,且需要长期维护的日志系统,应优先封装 UTL_CALL_STACK 而非沿用老函数。
PRAGMA EXCEPTION_INIT 用于捕获非预定义错误号
当遇到 Oracle 没给名字的错误(比如自定义触发器抛出的 ORA-20001),不能直接 WHEN e_my_err THEN,必须先绑定:
- 声明异常变量:
e_my_err EXCEPTION; - 用
PRAGMA EXCEPTION_INIT(e_my_err, -20001);将错误号映射到该变量 - 之后才能在
EXCEPTION块中用WHEN e_my_err THEN精准捕获
漏掉 PRAGMA 声明,WHEN 子句就永远匹配不到——这是上线后查不到错误日志的常见原因。
真正难的不是写 EXCEPTION 块,而是确保每次异常发生时,堆栈信息不被覆盖、不被截断、不被忽略。尤其在多层嵌套调用或封装了通用日志过程的场景下,FORMAT_ERROR_BACKTRACE 的调用时机和 UTL_CALL_STACK 的遍历逻辑,最容易出 silently wrong 的问题。


















