应使用自治事务写入日志表而非dbms_output.put_line,日志需包含过程名、错误码、错误信息、调用栈和时间,并记录上下文参数;务必显式commit,避免日志丢失。

存储过程里直接用 DBMS_OUTPUT.PUT_LINE 记不了错
很多人一上来就在异常块里写 DBMS_OUTPUT.PUT_LINE,结果发现程序跑完日志全丢——因为 DBMS_OUTPUT 是会话级缓冲输出,不提交、不持久化,PL/SQL 执行完就清空。线上出错根本看不到记录。
真正要落盘,必须写表。推荐建一张专用日志表,字段至少包含:log_id(序列)、proc_name(过程名)、error_code(SQLCODE)、error_msg(SQLERRM)、call_stack(用 DBMS_UTILITY.FORMAT_CALL_STACK)、log_time(SYSDATE)。
建表示例:
CREATE TABLE proc_error_log ( log_id NUMBER GENERATED BY DEFAULT AS IDENTITY, proc_name VARCHAR2(100), error_code NUMBER, error_msg VARCHAR2(4000), call_stack CLOB, log_time DATE DEFAULT SYSDATE );
EXCEPTION 块里别只写 RAISE 或 NULL
常见错误是:捕获异常后只做 RAISE;(原样抛出),或更糟——写个空 EXCEPTION WHEN OTHERS THEN NULL;,等于把错误吞掉,连日志都不留。
正确做法是在 EXCEPTION 块中先插入日志,再决定是否继续抛出:
- 业务关键路径建议记录后
RAISE;,让调用方感知失败 - 非关键分支(如异步通知失败)可记录后
RETURN;或继续执行 - 务必用
PRAGMA AUTONOMOUS_TRANSACTION独立事务写日志,否则主事务回滚时日志也跟着丢
示例片段:
CREATE OR REPLACE PROCEDURE my_proc AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
-- 业务逻辑
NULL;
EXCEPTION
WHEN OTHERS THEN
INSERT INTO proc_error_log (proc_name, error_code, error_msg, call_stack)
VALUES ('my_proc', SQLCODE, SQLERRM, DBMS_UTILITY.FORMAT_CALL_STACK);
COMMIT; -- 自治事务必须显式 COMMIT
RAISE; -- 保持原有错误传播行为
END;
日志内容要带上下文,不能只记 SQLCODE 和 SQLERRM
ORA-01403: no data found 这类错误本身没意义,得知道是查哪张表、用什么条件、哪个参数值触发的。否则排查时只能靠猜。
建议在日志插入前拼接关键变量,比如:
- 输入参数值(注意脱敏,避免记录敏感字段)
- 当前执行到的步骤标识(如
'before_update_order_status') - 关键中间值(如
v_order_id、v_customer_no)
可封装一个通用日志过程,减少重复代码:
PROCEDURE log_error(
p_proc_name IN VARCHAR2,
p_step IN VARCHAR2 DEFAULT NULL,
p_param1 IN VARCHAR2 DEFAULT NULL,
p_param2 IN VARCHAR2 DEFAULT NULL
) AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO proc_error_log (proc_name, error_code, error_msg, call_stack, log_time)
VALUES (
p_proc_name,
SQLCODE,
SQLERRM,
DBMS_UTILITY.FORMAT_CALL_STACK,
SYSDATE
);
COMMIT;
END;
别忽略自治事务的性能和锁风险
PRAGMA AUTONOMOUS_TRANSACTION 虽然能保证日志不被主事务回滚影响,但它会开启独立事务,可能带来两个隐性问题:
- 高并发下大量写日志表,容易成为瓶颈(尤其没建索引或分区)
- 如果日志表上存在未提交的 DML,可能阻塞其他自治事务写入
优化建议:
- 日志表加基于
log_time的分区(按月或按天) - 关键字段(如
proc_name、error_code)建组合索引,方便按过程名查错 - 生产环境考虑异步写日志(如通过 Advanced Queue 或外部消息队列),但会增加架构复杂度
最常被跳过的细节:忘记在自治事务里 COMMIT —— 不提交,日志就卡在事务里,既不落盘也不释放锁。











