直接查v$active_session_history,加sample_time过滤(如>sysdate-1/24)、event='enq: tx - row lock contention'条件,按sql_id+current_obj#分组聚合,并检查blocking_session字段定位元凶会话。
怎么从ash里快速揪出引发 enq: tx - row lock contention 的 sql?
直接查 v$active_session_history,别等 awr 报告生成完再动手——ash 是秒级采样,对刚发生的锁争用最敏感。关键不是“有没有”,而是“哪几条 sql 在反复撞同一行”。
- 必须加时间过滤:
sample_time > SYSDATE - 1/24(最近一小时),否则默认查全库历史,结果杂乱且慢 - 只筛真实等待的会话:
event = 'enq: TX - row lock contention',排除ON CPU或空闲会话干扰 - 聚合时按
sql_id+current_obj#分组,能一眼看出是哪条语句在哪个表上高频争用 - 别漏掉
blocking_session字段——它可能指向一个早先未提交的“元凶”会话,而不是当前报错的那条
dbms_rowid.rowid_create 为什么常返回空或报错?
这个函数本身不校验参数合法性,current_file# 和 current_block# 若来自已归档或被覆盖的 ASH 样本,就可能无效。更常见的是对象已重建、data_object_id 变了,但 ASH 还存着旧值。
- 先确认对象是否还存在:
SELECT owner, object_name FROM dba_objects WHERE object_id = :current_obj# - 若对象存在,再查
dba_objects.data_object_id是否匹配——重建表后object_id不变但data_object_id会变 - 用
DBMS_ROWID.ROWID_CREATE(1, :data_object_id, :file#, :block#, :row#)时,四个数字都得是正整数,任一为 NULL 或负数都会导致返回 NULL - 实际中建议加
WHERE current_file# > 0 AND current_block# > 0 AND current_row# >= 0过滤掉脏样本
为什么 ASH 能定位到行,而 v$session_wait 不能?
v$session_wait 只记录会话当前等待的事件和粗粒度资源(如 id1/id2),对 TX 锁只显示事务槽号,没法反推具体行;ASH 在每次采样时强制捕获 CURRENT_FILE#、CURRENT_BLOCK#、CURRENT_ROW#,这是 Oracle 内部用于快速定位物理位置的三元组。
-
v$session_wait的p1/p2对应 TX 锁的 undo segment# 和 slot#,需额外查v$transaction才能关联到会话 -
ASH的四个字段是“快照即所得”,只要采样时刻该会话正卡在行锁上,就能拿到物理地址 - 但注意:
CURRENT_ROW#是块内行号(0-based),不是逻辑行号;ROWID构造出来后,用SELECT * FROM tab WHERE ROWID = :rid才能查到真实数据
查到具体行之后,下一步最容易踩什么坑?
很多人拿到 ROWID 就去查数据,看到结果正常就以为没问题——其实问题往往藏在事务边界里。
- 先查这行所在的事务是否还开着:
SELECT start_time, used_ublk, status FROM v$transaction t JOIN v$session s ON t.ses_addr = s.saddr WHERE s.sid = :sid - 如果
status = 'ACTIVE'且used_ublk > 100,大概率是应用没提交,而不是 SQL 本身有问题 - 别急着 kill 会话——先用
SELECT sql_text FROM v$sql WHERE sql_id = :sql_id确认是不是业务关键事务(比如正在跑月结) - 真正要改的,常常不是 SQL,而是应用层:比如把单行更新改成批量处理,或加
SELECT ... FOR UPDATE NOWAIT提前暴露冲突
行锁争用的本质不是数据库慢,而是事务生命周期管理失控。ASH 给你的是物理坐标,但解法永远在代码和流程里——尤其当 seconds_in_wait 超过 30 秒时,90% 的情况是开发忘了 commit。











