v$global_blocked_locks默认不可用,需启用隐含参数_enable_global_blocked_locks=true;rac死锁应查gv$ges_blocking_enqueue定位源头节点,辅以lmd trace分析inst_id。
直接查 v$global_blocked_locks 会返回空?先确认它是否可用
oracle 官方文档明确说明:v$global_blocked_locks 在 rac 环境中**默认不可用**,它仅在启用隐含参数 _enable_global_blocked_locks=true 后才被填充数据,且该参数未通过 oracle 正式支持,19c 及以后版本甚至可能被移除。很多 dba 直接查这个视图却一无所获,根本原因不是 sql 写错,而是视图本身没开火。
- 执行
SELECT * FROM V$GLOBAL_BLOCKED_LOCKS;返回零行 ≠ 没有死锁,极大概率是视图未激活 - 检查是否启用:运行
SELECT ksppinm, ksppstvl FROM x$ksppi a, x$ksppcv b WHERE a.indx = b.indx AND ksppinm = '_enable_global_blocked_locks';,若ksppstvl为FALSE或空,则该视图无意义 - 不建议在生产环境临时设置该参数——它会强制 LMD 进程额外采集和维护全局阻塞链快照,增加 CPU 开销,且重启后失效
真正能定位“哪个节点引发死锁”的核心视图是 GV$GES_BLOCKING_ENQUEUE
这是 RAC 死锁分析的黄金入口,它跨实例反映全局队列(Global Enqueue)层面的真实阻塞关系,包含发起阻塞的实例号(INST_ID)、会话号(SID)、锁类型(TYPE)、资源标识(ID1/ID2)以及关键的 BLOCKING_INSTANCE 和 BLOCKING_SID 字段。
- 典型查询:
SELECT INST_ID, SID, SERIAL#, TYPE, ID1, ID2, BLOCKING_INSTANCE, BLOCKING_SID, STATE FROM GV$GES_BLOCKING_ENQUEUE WHERE BLOCKING_INSTANCE IS NOT NULL; -
BLOCKING_INSTANCE值就是“导致死锁的源头节点”,比如返回2,说明实例 2 上某个会话持锁未释放,成为整个环的起点 - 注意:该视图只显示“当前正在阻塞别人”的会话,不展示被阻塞者的完整等待链;需配合
GV$SESSION中的blocking_session和blocking_instance反向追溯 - 若查询结果为空但业务明显 hang 住,大概率是死锁尚未被 LMD 检测到(RAC 默认 60 秒检测周期),此时应立即抓取 AWR 快照或手动触发
oradebug dump global_ckpt 1强制诊断
从死锁 trace 文件里反推节点归属:看 _LMD_.trc 而非 _ORA_.trc
RAC 死锁不会生成单实例那种带完整 SQL 的 _ORA_<pid>.trc</pid>,而是由 LMD 进程写入集群级 trace,文件名形如 lmd0_*.trc,路径在 ADR 的 rdbms/<db_name>/<instance_name>/trace/</instance_name></db_name> 下。里面虽不直接写“实例1还是实例2”,但可通过资源持有者(holder)的 inst_id 和 sid 定位。
- 打开 trace 后搜索关键词
*** DUMP OF DEADLOCK TABLE ***,下面紧跟着的 block 节点信息中,每行开头类似inst: 2 sid: 456就是实际持锁节点 - 重点关注
Resource Name:行后的十六进制值(如TX-000a0012-000004d2),前四位000a是实例号(十进制 10),对应实例 10;RAC 中实例号即INST_ID - 不要依赖 trace 里的 SQL_TEXT —— RAC 的 LMD trace 通常只记录绑定变量占位符(如
:B1),真实 SQL 需结合当时GV$SQL中的sql_id和inst_id关联查询
为什么不能只靠 GV$SESSION.blocking_instance 判定“始作俑者”?
因为 GV$SESSION 只反映会话级阻塞快照,而 RAC 死锁本质是 GES 层的资源循环等待,存在“阻塞传递延迟”和“实例视角偏差”。一个会话在实例 1 上显示被实例 2 阻塞,不代表实例 2 就是源头——它自己可能正被实例 1 的另一个会话阻塞,形成跨实例闭环。
- 常见误判:看到
blocking_instance = 2就杀实例 2 的会话,结果发现它也在等实例 1,杀错对象反而延长 hang 时长 - 正确做法:以
GV$GES_BLOCKING_ENQUEUE为基础,找出所有BLOCKING_INSTANCE非空的记录,再对每个BLOCKING_INSTANCE+BLOCKING_SID组合,查其在GV$SESSION中是否也被他人阻塞;直到找到BLOCKING_INSTANCE IS NULL的那个会话,才是真正的根因节点 - 容易忽略的一点:某些死锁由 ITL 争用或位图索引更新引发,这类锁在
GV$SESSION中 event 显示为enq: TX - allocate ITL entry或enq: TX - row lock contention,但BLOCKING_INSTANCE可能为 NULL——此时必须查GV$TRANSACTION和GV$LOCK中的INST_ID和LMODE才能定位
RAC 全局死锁的根因从来不在“哪个 SQL”,而在“哪个实例上哪个会话持锁不放”,而这个会话往往不是第一个执行 UPDATE 的,而是最后一个提交失败、回滚滞后、或事务异常中断残留的。别信视图名,信 LMD trace 里的 inst_id,信 GES 队列里的 blocking_instance,其它都是干扰项。










