必须用SQLCODE和SQLERRM获取错误编号与消息,否则WHEN OTHERS仅掩盖问题;真正定位需DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,且须在WHEN OTHERS首行调用、不可与RAISE连用。

必须用 SQLCODE 和 SQLERRM 拿到错误编号和消息,否则 WHEN OTHERS 只是假装处理了异常。
WHEN OTHERS 不等于“已记录错误”
写了 WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('出错了') 并不意味着你捕获到了真实错误。PL/SQL 的 WHEN OTHERS 本身不携带任何错误信息——它只是个兜底开关。没读 SQLCODE 和 SQLERRM,等于只听见“砰”一声,却不知道哪颗螺丝松了。
-
SQLCODE返回负数错误码(如-1403表示NO_DATA_FOUND),成功时为0,但在EXCEPTION块里永远非零 -
SQLERRM返回带ORA-前缀的字符串(如ORA-01403: no data found),默认最多 4000 字符,入库前建议SUBSTR(SQLERRM, 1, 3999) - 二者只在当前
EXCEPTION块内有效;跳出去调用就变0和空串 - 常见错误:把
SQLCODE赋值给变量后传进另一个过程再读——此时已失效
真正定位问题得靠 DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
SQLERRM 只告诉你“什么错”,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
这个函数有三个硬性约束:
- 必须放在
WHEN OTHERS的第一行,不能先做事务回滚或日志写入——否则堆栈被覆盖 - 不能跟
RAISE连用:RAISE会重置堆栈,返回的是RAISE所在行,不是原始错误行 - 若要封装后重新抛出,改用
RAISE_APPLICATION_ERROR(-20001, msg, TRUE),第三个参数TRUE表示保留原始堆栈
Oracle 12c+ 推荐用 UTL_CALL_STACK 辅助分析
UTL_CALL_STACK 不是替代 FORMAT_ERROR_BACKTRACE,而是提供结构化访问能力:能逐层读深度、取对象名、行号、调用类型。典型用法:
-
UTL_CALL_STACK.DYNAMIC_DEPTH获取当前堆栈总深度 -
UTL_CALL_STACK.UNIT_LINE(1)取最内层(即错误发生处)的行号 -
UTL_CALL_STACK.OWNER(1) || '.' || UTL_CALL_STACK.UNIT_NAME(1)拼出完整对象引用(如SCOTT.PROC_INSERT) - 它本身不捕获“错误发生点”,仍需配合
FORMAT_ERROR_BACKTRACE使用才能还原上下文
Spring Boot 调用存储过程时堆栈容易被截断
JDBC 驱动默认只暴露 SQLException.getMessage(),原始 PL/SQL 堆栈被砍掉一大截。比如只看到 ORA-06550: line 1, column 7,但看不到 at PKG_UTILS line 45 这类关键路径。
- 解决办法:在 PL/SQL 层就把完整堆栈拼好,例如:
SQLERRM || CHR(10) || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE - 然后通过 OUT 参数或自定义错误消息(
RAISE_APPLICATION_ERROR)传回 Java 层 - Java 端不要只取
e.getMessage(),而应检查e.getErrorCode()和完整异常文本
最常被忽略的一点:DBMS_UTILITY.FORMAT_ERROR_BACKTRACE 和 DBMS_UTILITY.FORMAT_ERROR_STACK 完全不同用途,混用会导致日志里只有错误码,没有行号——排障时只能靠猜。


















