不能。ORA-00060 是运行时死锁错误,不可被预定义异常捕获,须用 SQLCODE = -60 精准判断;记录日志需在自治事务中获取 SESSIONID、SQL 片段和调用栈,并用 SYSTIMESTAMP 记时。
ORA-00060 报错时,EXCEPTION 块里能直接捕获吗?
不能。pl/sql 中 ora-00060 是死锁检测后由 oracle 主动回滚并抛出的运行时错误,但它不属于可被标准 exception 块捕获的预定义异常(比如 no_data_found 或 too_many_rows)。你写 when others then 能兜住,但无法区分是不是死锁;想精准识别,必须用 sqlcode 判断。
-
SQLCODE在死锁发生时返回-60,这是唯一稳定可靠的判断依据 - 不要依赖错误消息文本(如
SQLERRM含 “deadlock detected”),不同语言环境或补丁版本可能微调措辞 - 如果在存储过程中没做
SQLCODE = -60检查,就等于把死锁当普通错误吞掉了,后续日志里看不到上下文
怎么在 PL/SQL 里记录死锁发生时的关键现场信息?
光记“ORA-00060”没用,得立刻抓三样东西:当前会话 ID、执行的 SQL 片段、事务起始时间。Oracle 提供了现成函数,但要注意调用时机——必须在 EXCEPTION 块里、且在事务已回滚之后(否则 DBMS_TRANSACTION.LOCAL_TRANSACTION_ID 可能为空)。
- 用
SYS_CONTEXT('USERENV', 'SESSIONID')拿会话号,比USERENV('SID')更稳,兼容 RAC -
DBMS_UTILITY.FORMAT_CALL_STACK能输出调用链,但别直接存全量(太长),建议截取前 500 字符 - 记录
SYSDATE不够准,改用SYSTIMESTAMP,避免跨秒边界丢失毫秒级时序 - 日志表字段设计要有
sql_text VARCHAR2(1000),别只存SQL_ID——死锁常发生在动态拼接 SQL 场景,没原文很难复现
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -60 THEN
INSERT INTO deadlock_log (
session_id, sql_snippet, call_stack, log_time
) VALUES (
SYS_CONTEXT('USERENV', 'SESSIONID'),
SUBSTR(:sql_text, 1, 1000),
SUBSTR(DBMS_UTILITY.FORMAT_CALL_STACK, 1, 500),
SYSTIMESTAMP
);
END IF;
RAISE; -- 仍需抛出,不能静默
为什么在自治事务(PRAGMA AUTONOMOUS_TRANSACTION)里记日志更安全?
因为死锁触发后,当前事务已被 Oracle 强制回滚,任何非自治的 DML(比如往日志表插记录)都会报 ORA-06519:active autonomous transaction detected and rolled back。只有显式声明自治事务,才能绕过这个限制,确保日志一定落地。
- 自治事务和主事务完全隔离,提交/回滚互不影响
- 但代价是:自治事务里查不到主事务未提交的数据,所以别试图在日志里记录“本该插入但失败的业务数据”
- 必须在自治过程开头加
COMMIT,否则日志写入会卡住(自治事务不自动提交) - 别在包头声明自治,一定要在具体写日志的过程体里写
PRAGMA AUTONOMOUS_TRANSACTION,否则整个包都变自治,容易误伤
死锁日志写完就完事了?还得检查哪些隐性依赖?
写了日志只是第一步。真正麻烦的是后续分析:如果日志表用了 LOGGING 模式 + 索引,高并发死锁爆发时,日志写入本身可能成为新瓶颈,甚至引发连锁死锁。
- 日志表务必设为
NOLOGGING(只要能接受实例崩溃丢失最后几条日志) - 删除所有非必要索引,最多保留一个按
log_time的分区索引,否则每次 INSERT 都要更新索引块 - 定期归档(不是删除!),用
DBMS_SCHEDULER每小时跑一次分区交换,避免大表扫描拖慢写入 - 如果应用层用了连接池(如 UCP),确认池配置里
abandonedConnectionTimeout大于死锁检测周期(默认 3 秒),否则连接可能在日志写入前就被池回收
死锁现场稍纵即逝,SQLCODE = -60 是唯一锚点;日志内容是否可用,取决于你有没有在事务回滚前抢到那几个关键函数的调用时机。

















