直接查v$active_session_history可秒级定位行锁争用sql,ash是唯一能捕获刚发生tx锁争用的视图,结合blocking_session和p1/p2可精准追溯持有者与具体行。
直接查 v$active_session_history 就能定位到刚发生的行锁争用源头,别等 awr 报告——ash 是唯一能秒级捕获的视图,只要事件在最近一小时内出现过,就能揪出反复撞同一行的 sql_id。
查 ASH 快速锁定高频争用 SQL
等待事件不是孤立现象,关键是找出「哪几条 sql_id 反复卡在同一个对象上」。ASH 的 sample_time 过滤必须加,否则扫全库历史既慢又不可靠:
-
sample_time > SYSDATE - 1/24是硬性条件,ASH 内存默认只保留约 1 小时 -
current_obj#是对象编号,不是object_id——表重建后它可能已失效,后续构造 ROWID 前得先校验 - 若
sql_id为空,大概率是 PL/SQL 匿名块或解析失败,此时要结合program和module字段人工判断来源
执行这个语句:
SELECT sql_id, current_obj#, COUNT(*) cnt FROM v$active_session_history WHERE sample_time > SYSDATE - 1/24 AND event = 'enq: TX - row lock contention' AND session_type = 'FOREGROUND' GROUP BY sql_id, current_obj# ORDER BY cnt DESC;
用 blocking_session 反向追踪持锁会话
等待方只是“被堵者”,真正卡住它的往往是早先未提交的事务。blocking_session 字段只在采样瞬间有效,不能只查一次就下结论:
- 执行时加窄时间窗口(比如
SYSDATE - 5/(24*60)),确保拿到的是同一波争用现场 - 若发现 A 的
blocking_session = B,而 B 的blocking_session = A,就是死锁雏形,立刻查这两会话的sql_text和事务起始时间 -
p1和p2是 TX 锁的关键:对 mode=6(标准行锁),p1解出是TX+ 模式,p2/p3组合可反查v$transaction定位真实事务;若两个会话互为p1/p2,基本可确认互锁
执行这个联合查询:
SELECT sample_time, sid, blocking_session, sql_id, event, p1, p2
FROM v$active_session_history
WHERE sample_time > SYSDATE - 5/(24*60)
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 - 5/(24*60)
AND blocking_session IS NOT NULL));
区分 mode=4 和 mode=6,处理方式完全不同
enq: TX - row lock contention 的 p1raw 值决定本质问题:54580006 是 mode=6(标准行锁等待),54580004 是 mode=4(非行锁语义冲突):
- mode=6:A 更新某行没提交,B 想更新同一行就被卡住。解决路径清晰——让 A 提交/回滚,或杀掉 A
- mode=4:通常不是“别人锁了这行”,而是唯一键冲突、ITL 不足或位图索引并发修改。此时 kill 等待者无效,因为冲突根源在业务逻辑层
- 唯一键冲突典型表现:两个会话同时
INSERT INTO t(pk) VALUES (1),第二个会等锁释放,但释放后立刻报ORA-00001——等待只是表象,根本问题是业务没做幂等或前置校验 - ITL 不足可通过
dba_segments查ini_trans/max_trans,或加ALTER TABLE ... STORAGE (INITRANS 16)缓解
构造 ROWID 定位具体行时最容易错的三件事
拿到 current_file#、current_block#、current_row# 后,用 DBMS_ROWID.ROWID_CREATE 构造 ROWID 是最后一步,也是最易翻车环节:
- 误把
current_obj#当成object_id直接传给DBMS_ROWID.ROWID_CREATE——它实际需要的是data_object_id,得先查dba_objects.data_object_id - 忽略
current_file#是相对文件号(relative_fno),不是绝对文件号(file_id),跨表空间时会指向错误数据文件 -
current_row#是块内行号(0-based),但某些版本的DBMS_ROWID.ROWID_CREATE参数要求从 1 开始,传错会导致 ROWID 解析失败
安全做法是先查出完整上下文再构造:
SELECT s.sid, o.owner||'.'||o.object_name locked_object,
DBMS_ROWID.ROWID_CREATE(1, o.data_object_id, s.row_wait_file#, s.row_wait_block#, s.row_wait_row#) locked_rowid
FROM v$session s, dba_objects o
WHERE s.event = 'enq: TX - row lock contention'
AND s.row_wait_obj# = o.object_id;
真正难的不是查到哪条 SQL 在等,而是确认它等的是「物理行锁」还是「语义冲突」——mode=4 和 mode=6 的根因完全不同,但错误日志里只显示同一个等待事件名。











