必须结合v$session的blocking_session/final_blocking_session与ash的时间窗口采样才能准确定位持锁者、等待者及sql;单独查任一视图易误判。
直接看 v$session 和 ash 就能定位到谁在 hold 锁、谁在等、sql 是哪条——但必须限定时间窗口,否则数据太杂,容易误判。
查当前正在等 TX 锁的会话(v$session 实时快照)
这个视图反映的是“此刻”正在等待的会话,适合快速响应。关键不是只看 event = 'enq: TX - row lock contention',而是要连带看阻塞链:
-
blocking_session和final_blocking_session字段必须一起查,12c+ 推荐优先看后者,它指向最终持有锁的会话(可能跨实例) -
p1的低 16 位是锁模式(to_char(bitand(p1, 65535))),常见值 6=exclusive,4=share;但实际排查中更依赖sql_id和blocking_session - 别漏掉
last_call_et:如果值很大(比如 > 300),说明这个会话可能事务没提交,已空闲挂起很久 - 执行前先
set linesize 200,否则sql_id和program显示不全
用 ASH 抓故障窗口内的锁争用热点(dba_hist_active_sess_history)
AWR 报告里的 Segments by Row Lock Waits 只告诉你“哪张表被锁得多”,但无法还原“谁、什么时候、为什么锁”。ASH 才是定位源头的核心:
- 必须指定精确时间范围(比如
sample_time between timestamp'2026-04-29 09:45:00' and timestamp'2026-04-29 09:50:00'),Oracle 19c 默认每秒采样一次,5 分钟就是 300 条记录,足够定位 - 重点查:
sql_id、current_obj#(关联dba_objects.object_id看具体表)、blocking_session、event四列组合 - 如果发现多个
sql_id都在等同一个blocking_session,那它大概率就是事务未提交的“罪魁”;如果多个sql_id互等(A 等 B、B 等 C、C 等 A),可能是死锁,但enq: TX本身不触发 Oracle 自动死锁检测,得靠应用层或 DBA 主动 kill - 注意
session_state = 'WAITING'是前提,ON CPU或INACTIVE的行直接过滤掉
确认 holder 会话到底在干什么(v$sql + v$transaction)
找到 blocking_session 后,不能只看它当前执行的 SQL —— 它很可能早已执行完 DML,只是没 commit:
- 查
v$transaction:用ses_addr关联v$session.saddr,看start_time和used_ublk,如果start_time是 20 分钟前,used_ublk > 0,基本坐实“长事务未提交” - 查
v$sql:用 holder 的sql_id查历史执行,重点看是否含UPDATE或DELETE,且executions很小但elapsed_time很大 —— 典型慢 SQL 持有锁太久 - 别信
v$session.sql_id当前值:它可能显示为NULL(事务空闲中),此时得回溯v$transaction.start_scn去查dba_hist_sqlstat - 19c 中可结合
dbms_workload_repository.ash_report_text生成文本版 ASH 报告,用grep 'enq: TX'快速筛出所有相关样本行
容易被忽略的三个细节
很多排查卡在最后一步,不是因为不会查,而是忽略了这些隐性条件:
-
enq: TX的 P2/P3 参数在 19c 中含义变了:P2 不再是rowid,而是 transaction ID 的高位/低位,直接解析无意义;想定位具体行,得用dbms_rowid.rowid_create结合v$locked_object的object_id+session_id反查 - 位图索引更新引发的 TX 锁,
current_obj#指向的是索引而非表,容易误判成“锁了错误的表” - 唯一键冲突(如重复插入)产生的 TX 锁,等待会话的
sql_id往往和 holder 的不同,且 holder 可能已 rollback,此时v$session.blocking_session为空,只能靠 ASH 中event+sql_id+sample_time三者交叉比对











