查死锁应优先用ASH而非v$session_wait,因其每秒采样可还原等待链;需限定1分钟内、TX锁事件、同时筛选等待方和持有方,并通过blocking_session互指及p1/p2交叉匹配识别互锁会话对。
查死锁不能只看 v$session_wait,ASH 才有完整等待链快照
死锁发生后,oracle 会自动记录到 trace 文件并清理锁状态,v$session_wait 只保留“当前”等待,等你连上去时早已清空。而 ash 在死锁发生的每一秒都可能采样到至少一个参与会话的阻塞状态,只要没被环形缓冲区覆盖,就能还原链路。
关键不是“有没有死锁”,而是“哪几个会话在那一秒同时卡在 TX 锁上、且互指对方为 blocking_session”。ASH 的 blocking_session 字段是实时抓取的,比事后查 v$lock 更可靠。
- 必须加时间过滤:
sample_time BETWEEN SYSDATE - 1/1440 AND SYSDATE(最近 1 分钟),死锁持续时间极短,范围太大会淹没信号 - 只筛真实冲突事件:
event IN ('enq: TX - row lock contention', 'enq: TX - allocate ITL entry'),排除单边等待干扰 - 要同时查等待方和持有方:
WHERE blocking_session IS NOT NULL OR session_state = 'WAITING',否则漏掉已释放但刚被采样的持有者 - 注意
blocking_session_serial#必须和blocking_session成对出现,单独blocking_session为 NULL 时不可信
v$active_session_history 中怎么识别互锁会话对
死锁本质是两个及以上会话形成循环等待:A 等 B 持有的行,B 又等 A 持有的行。ASH 不会直接标出“这是死锁”,但你能从采样数据里人工拼出这个环。
做法是把同一秒内、同一 sample_time 下满足以下条件的行拎出来:
- 会话 A 的
blocking_session = B.sid,且 B 的blocking_session = A.sid - A 和 B 的
event都是enq: TX - row lock contention - A 和 B 的
current_obj#可能不同(锁不同行),但sql_id往往相同或高度相似(同业务逻辑) - 检查
p1(undo segment#)和p2(slot#):若 A.p1 = B.p2 且 B.p1 = A.p2,基本可确认 TX 锁互持
示例查询片段:
SELECT sample_time, sid, blocking_session, event, p1, p2, sql_id, current_obj#
FROM v$active_session_history
WHERE sample_time > SYSDATE - 1/1440
AND event = 'enq: TX - row lock contention'
AND (blocking_session IS NOT NULL OR sid IN (
SELECT blocking_session FROM v$active_session_history
WHERE sample_time > SYSDATE - 1/1440 AND blocking_session IS NOT NULL
))
ORDER BY sample_time DESC, sid;
为什么 dba_hist_active_sess_history 不适合查刚发生的死锁
dba_hist_active_sess_history 是 AWR 快照落地的数据,默认每 10 秒汇总一次,且只存最近 7–8 天(取决于 AWR 保留策略)。而真实死锁往往在毫秒级完成、日志写入后立即回滚,ASH 内存里的原始秒级样本才是唯一证据源。
-
dba_hist_active_sess_history的sample_time是四舍五入到最近 10 秒的,比如死锁发生在12:34:56.789,它只会记成12:35:00,丢失关键时序 - 它的
blocking_session字段在部分版本中为空,因为 AWR 汇总时做了聚合丢弃 - 内存视图
v$active_session_history的采样精度是 1 秒,且字段完整,只要没被覆盖(默认约 30MB 内存,高负载下可能只剩几分钟)就可用 - 如果怀疑已被覆盖,立刻执行
EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT强制刷一次快照,抢救残余数据
拿到互锁会话后,怎么快速定位到具体 SQL 和行
查到 A/B 会话 ID 后,别急着 kill,先确认它们到底在改什么数据——很多“死锁”其实是应用层重试逻辑缺陷,而非 SQL 本身问题。
- 用
sql_id查原始语句:SELECT sql_text FROM v$sql WHERE sql_id = ',注意检查是否含' FOR UPDATE或未提交的 DML - 构造 ROWID 定位物理行:
DBMS_ROWID.ROWID_CREATE(1, data_object_id, file#, block#, row#),四个参数必须全为正整数,且data_object_id要从dba_objects查最新值(表重建后object_id不变但data_object_id会变) - 验证行是否还存在:
SELECT * FROMWHERE ROWID = '
',若返回空,说明该行已被其他事务删除或更新,死锁只是瞬态现象 - 重点看事务持续时间:
SELECT start_time, used_ublk FROM v$transaction WHERE ses_addr IN (SELECT saddr FROM v$session WHERE sid IN (A,B)),长时间未提交的事务是死锁温床最易忽略的一点:ASH 里的
CURRENT_ROW#是块内偏移(0-based),不是表里逻辑行号;用它构造的 ROWID 只能查到那一刻的物理位置,若该块后续被分裂或迁移,结果可能不准。真正稳的方式,是结合sql_id+ 绑定变量 + 应用日志反推业务上下文。 - 重点看事务持续时间:











