_gc buffer busy acquire 和 gc cr request 是 RAC 中一对信号-响应等待事件:前者是本地实例获取current块被阻塞,后者是为构造CR块向远程发起的请求;根因多为远程实例长期持有current块或网络延迟。
什么是 _gc buffer busy acquire 和 gc cr request?
这两个不是独立的等待事件,而是 rac 中一对紧密耦合的“信号-响应”行为:_gc buffer busy acquire 是本地实例在尝试获取某个数据块的当前(current)版本时被阻塞,而 gc cr request 是它为生成一致性读(cr)块向远程实例发起的跨节点请求。真正的问题往往不在等待本身,而在“为什么 cr 请求迟迟不返回”或“为什么 current 块长期被占着”。
常见错误现象包括:
- 某个 SQL 在 RAC 上执行明显变慢,AWR 报告中
_gc buffer busy acquire和gc cr request同时排进 Top 5 -
gc cr request的平均等待时间(ms)持续高于 10–20ms,远超局域网 RTT(通常应 < 1ms) -
v$session_wait中看到大量会话卡在_gc buffer busy acquire,p1(file#)、p2(block#)值高度集中
根本原因通常是:远程实例持有该块的 current 版本未及时释放(比如长事务、未提交更新、高并发 DML 锁争用),或网络/IPC 层延迟异常,导致 CR 构造失败或超时重试。
怎么定位是哪个实例、哪个对象在“卡住”CR 流程?
核心思路是逆向追踪:从等待的会话出发,查它想拿的块,再查谁正拿着这个块的 current 版本。
- 在等待
_gc buffer busy acquire的会话上,查v$session的p1(file#)、p2(block#)、p3(class#),拼出绝对文件号和块号 - 用
dbms_utility.data_block_address_file和dbms_utility.data_block_address_block解析p1/p2(如果已是绝对值可跳过) - 查询
gv$bh找出当前持有该file#/block#的实例(inst_id)和状态(state= 'xcur' 或 'cr') - 关联
gv$session查该实例上row_wait_obj#或sql_id对应的活跃会话,重点关注未提交事务(status= 'ACTIVE' 且tx_state不为 'IDLE')
示例片段(在等待实例上运行):
SELECT inst_id, state, dirty, temp, for_read, hash_chain#, tch
FROM gv$bh
WHERE file# = &file_id AND block# = &block_id AND state IN ('xcur', 'cr');注意:不要只看 gv$lock,DML 引起的 buffer busy 往往不走行锁路径,而是 buffer pin 冲突,gv$bh 更直接。
为什么加大 _gc_policy_time 或调高 gc_files_to_locks 通常没用?
这是最常见的误操作。这两个参数针对的是全局缓存策略调度频率和锁粒度,和 buffer busy 的直接成因无关:
-
_gc_policy_time控制 LMS 进程扫描 cache fusion 状态的间隔,默认 10 秒;调小它只会让 LMS 更频繁“发现”问题,不解决 block 被占住的事实 -
gc_files_to_locks影响每个数据文件分配多少 global lock,用于控制并发访问粒度;但 buffer busy 是内存 buffer pin 竞争,发生在cache buffers chainslatch 层,和 global lock 无直接关系
真正该检查的配置项:
-
cluster_database_instances是否与实际节点数一致(不一致会导致 GC 路由异常) -
_gc_affinity_time如果设得过大(如 > 3600),可能加剧热点块的局部化,反而放大 busy 等待 - 网络层面:确认私网 MTU 是否一致、是否启用了 jumbo frame、交换机是否有丢包(
netstat -s | grep -i "packet.*loss")
最有效的干预手段其实是“避开”而不是“加速”
与其花时间优化 GC 参数,不如快速判断能否绕过争用点:
- 如果是索引块热块(如序列主键索引),立刻检查是否用了
REVERSE KEY或DESC索引分散插入热点 - 如果是小表全表扫描引发的 CR 风暴,确认是否加了
/<em>+ full(t) </em>/提示强制走全扫,考虑加索引或改写逻辑 - 如果是单条 UPDATE 长时间未提交,直接 kill 对应会话(
alter system kill session '&sid,&serial' immediate),比等它自己释放快得多 - 对 OLTP 类型的频繁小事务,确保应用层使用
COMMIT及时释放 buffer pin,避免显式开启大事务(SET TRANSACTION ISOLATION LEVEL SERIALIZABLE)
真实场景中,80% 的 _gc buffer busy acquire + gc cr request 组合,根因是某一个实例上的某个会话长时间 pin 住几个关键块——找到它,干掉它,等待就立刻消退。
复杂点在于:block# 相同但 file# 是相对号时容易查错对象;更隐蔽的是 temp 表空间或 undo 段块争用,它们不会出现在普通对象查询里,得专门查 ts# 和 segment_type。










