查v$active_session_history必须加SAMPLE_TIME和session_state='WAITING'双过滤,且需满足采样在问题时段、会话当前正被阻塞;RAC下应优先使用final_blocking_session与final_blocking_instance定位真实持锁源,并通过连续sample_id追踪阻塞链,结合v$transaction验证长事务未提交。
查 v$active_session_history 必须加时间窗口和等待状态过滤
不加 sample_time 限定的 ash 查询会扫全内存 buffer,轻则卡住,重则因权限不足报错。只筛 blocking_session is not null 但不校验是否真在等,容易把已断开的僵尸会话当根因。
实操必须同时满足两个前提:
-
SAMPLE_TIME > SYSDATE - 3/1440(最近 3 分钟),避免全量扫描 -
session_state = 'WAITING'且event = 'enq: TX - row lock contention',排除 ON CPU、INACTIVE 或已完成的样本
漏掉任一条件,结果就不可信——比如你看到一个 blocking_session = 123,但它其实在 5 分钟前就提交了,当前只是残留记录。
区分真实阻塞者:别只看 blocking_session,要盯 final_blocking_session
v$session.blocking_session 在 RAC 下可能指向本节点的中间阻塞者,而真正持锁的会话在另一个实例上。final_blocking_session 和 final_blocking_instance 才是 Oracle 12c+ 推荐的字段,它跳过中间层,直指源头。
常见错误是查到 A → B → C 的链,就 kill B,结果 C 还在等 A 持有的锁,问题没解。正确做法:
- 优先查
final_blocking_session IS NOT NULL的行 - RAC 环境下必须连带查
final_blocking_instance,并确认该实例号是否有效(用SELECT instance_number FROM v$instance校验) - 如果
final_blocking_session是 0 或空,说明它是根阻塞源,不是被别人阻塞的
还原阻塞链不能靠 CONNECT BY,得用 sample_id 递减追踪
ASH 是滚动内存表,每秒一条记录,sample_id 严格递增。用 CONNECT BY PRIOR blocking_session = session_id 会强行把不同时刻的会话拼在一起,比如把上午 9:45 的会话 A 和下午 3:20 的会话 B 错误关联。
真实阻塞路径得聚焦同一会话在连续时刻的行为:
- 先锁定一个高频等待的
session_id和session_serial# - 查它前后几秒的样本:
SAMPLE_ID BETWEEN X-2 AND X+2 - 观察 event 是否出现
enq: TX - row lock contention→db file sequential read→cursor: pin S wait on X这类典型连锁
如果多个会话都指向同一个 sql_id,且该 SQL 操作的是同一张表的主键或唯一索引列,优先检查事务是否未提交,或是否存在隐式锁升级。
确认持锁会话到底在干什么:v$transaction 比 v$sql 更可靠
找到 final_blocking_session 后,别急着看它当前的 sql_id——那很可能已是空闲状态,SQL 已执行完,只是没 commit。真正坐实“长事务未提交”的证据在 v$transaction。
关键操作步骤:
- 用
v$session.saddr关联v$transaction.ses_addr - 看
start_time:如果比当前时间早 10 分钟以上,基本可判定事务挂起很久 - 看
used_ublk:大于 0 表示还在用回滚段,锁没释放 - 再查
v$sql中该会话历史执行的 SQL,重点找executions = 1但elapsed_time很大的 UPDATE/DELETE
最易忽略的一点:current_obj# 在 ASH 中可能指向已重建的表——object_id 不变但 data_object_id 已变,用它构造 ROWID 前务必先查 dba_objects 确认对象是否存在且 data_object_id 匹配。


















