查 enq: TX - row lock contention 必须加时间窗和事件过滤,用 gv$active_session_history(RAC)、按 sql_id/current_obj#/sql_opname 分组,识别 mode=6 真行锁与 mode=4 假行锁,定位 final_blocking_session 并验证 v$sql 执行次数。

查 enq: TX - row lock contention 必须加时间窗和事件过滤
不加条件直接查 v$active_session_history,结果基本不可用——大量后台采样、空闲会话、网络等待混在里面,真正持锁的样本被稀释到看不见。必须同时满足:
-
event = 'enq: TX - row lock contention'(大小写敏感,不能漏短横线或写成小写) -
sample_time > SYSDATE - INTERVAL '5' MINUTE(19c/12c 用 5 分钟足够;11g 可放宽至 10 分钟,但别用SYSDATE - 1/24,NLS 设置可能让时间漂移) - RAC 环境下必须用
gv$active_session_history,否则只看到本节点,而持锁者可能在 node2
聚合时按 sql_id 和 current_obj# 分组,别只盯 sql_text
同一业务逻辑的 SQL 若未绑定变量,sql_text 微小差异就会生成不同 sql_id,但它们操作的表(current_obj#)往往高度一致。高频行锁等待通常集中在某张核心表上,比如连续多行 current_obj# = 56789,立刻去查 dba_objects 确认表名。
- 不要
GROUP BY sql_text—— 容易把同一条 UPDATE 拆成几十个“不同语句” - 要
GROUP BY sql_id, current_obj#, sql_opname——sql_opname能区分是 INSERT 还是 UPDATE,避免忽略带子查询的 DML - 若
current_obj#为空,说明等待发生在解析/PL/SQL 层,不是行级争用,应转向plsql_entry_object_id分析
识别真行锁(mode=6)还是假行锁(mode=4)
从 v$session 查到等待事件是 enq: TX - row lock contention 后,别急着 kill。先看该会话的 p1raw 值:
- 若
p1raw = '54580006'(后两位是 06)→ mode=6 → 标准行锁:A 更新某行未提交,B 等同一行 → 查 A 的sql_id和事务起始时间 - 若
p1raw = '54580004'(后两位是 04)→ mode=4 → 很可能不是行锁,而是唯一键冲突或 ITL 不足 → 即使杀掉 B,下一个 INSERT 仍报ORA-00001 - 验证是否唯一键冲突:检查对应
sql_id的语句是否为INSERT INTO ... VALUES (...),且current_obj#指向唯一索引所在表
定位持锁者不能只看 blocking_session,得查 final_blocking_session
RAC 下 blocking_session 只反映本实例内的阻塞链,常是中间节点。真正没提交的会话可能在另一节点,根本不出现在当前实例的 v$session 里。
- 必须查
gv$active_session_history,并显式选择final_blocking_session和final_blocking_instance - 若
final_blocking_session = 0或为 NULL,说明它就是根因——90% 是那个跑完UPDATE /*+ PARALLEL */ WHERE batch_id = ?就没COMMIT的批处理会话 - 查到
final_blocking_session后,立刻关联gv$session看其sql_id、program(常见为oracle@host (J000))、logon_time,确认是否长时间未提交
最常被跳过的一步:拿到 sql_id 后,不查 v$sql 里的 executions 和 loads。如果 executions = 1 但 loads > 5,基本坐实是硬解析引发的共享池争用,和行锁无关——这时候你查的其实是噪音。











