可通过查询V$ACTIVE_SESSION_HISTORY按P1分组识别真实热块,再结合DBA_EXTENTS定位对象;加低选择性索引易引发回表集中访问导致CBC争抢加剧;调整_db_block_hash_buckets等参数可优化latch竞争。
怎么看 latch: cache buffers chains 是不是真热点
直接查 dba_hist_active_sess_history 或实时 v$active_session_history,过滤 event = 'latch: cache buffers chains',再按 p1(即 buffer#)分组看谁最常被争抢。别只盯着“等待次数高”,得看是否集中在同一个 file# + block# ——用 dba_extents 或 dbms_utility.data_block_address_file/block 反查对象,确认是不是某个小表的索引根块、或主键索引叶块。
常见错误是把多个不同 block 的 CBC 等待全归为“热块”,其实只是 latch 拆分粒度不够导致的假象;真正要命的是单个 block 被成千上万次访问且无法并发读(比如没开 optimizer_adaptive_features 下的 nested loop 驱动表全扫)。
- 查热点 block:
SELECT p1, COUNT(*) FROM v$active_session_history WHERE event = 'latch: cache buffers chains' GROUP BY p1 ORDER BY 2 DESC FETCH FIRST 5 ROWS ONLY - 反查对象:
SELECT owner, segment_name, segment_type FROM dba_extents WHERE file_id = :file AND :block BETWEEN block_id AND block_id + blocks - 1 - 注意:如果
p1值来回跳变但范围窄(如只在几个连续 block),可能是索引分支扫描路径固定,不一定是数据层热块
为什么加索引反而让 cache buffers chains 更忙
典型场景是给高频查询字段加了非选择性索引(WHERE status IN ('A','B') 这种),导致执行计划走 INDEX RANGE SCAN + TABLE ACCESS BY INDEX ROWID,大量回表访问同一堆表块 —— 尤其当堆表本身很小时,所有行挤在头几个 data block,CBC latch 争抢就集中在那几个 buffer header 上。
这时候索引没减少逻辑读,反而增加了索引块本身的 CBC 争抢(根块/分支块被反复访问),属于双倍开销。
- 检查执行计划中是否有高估的
ROWS和低效的ACCESS PREDICATES,特别是对低基数列建的索引 - 用
DBMS_STATS.LOCK_TABLE_STATS锁住统计信息,手动改num_distinct测试执行计划变化 - 对比加索引前后
v$latch_children中cache buffers chains的gets和spin_gets比例:spin_gets 占比 > 30% 就说明自旋失败多,竞争已严重
哪些参数和配置会掩盖或加剧 CBC latch 问题
_db_block_hash_buckets 太小(默认是 db_cache_size / 512 向下取整)会导致多个 block 映射到同一个 hash bucket,进而共享一个 CBC latch,放大争抢;而设太大又浪费内存、增加 hash 查找开销。Oracle 12c+ 默认启用 adaptive hash latch,但仅在 latch child count 动态扩展后才生效,初始化时仍可能不足。
另一个关键是 db_block_size:8K 块比 16K 块更容易出现“单 block 高频访问”,因为同样数据量下 block 数翻倍,hash bucket 冲突概率上升。
- 查当前设置:
SELECT ksppinm, ksppstvl FROM x$ksppi JOIN x$ksppcv USING (indx) WHERE ksppinm LIKE '%hash%bucket%' - 临时调大(需重启):
ALTER SYSTEM SET "_db_block_hash_buckets" = 65536 SCOPE=SPFILE,但别超过db_cache_size / 2048 - 禁用自适应(仅诊断用):
ALTER SYSTEM SET "_latch_adaptive_scaling" = FALSE,观察 latch child 分布是否更均匀
SQL 层能做的最小代价缓解动作
不改表结构、不加硬件的前提下,最有效的是切断“重复访问同一 block”的循环路径。比如把嵌套循环驱动表换成哈希连接,或者强制用 /*+ full(t) */ 避免索引回表;对极小维表,考虑 /*+ no_use_nl(t) use_hash(t) */ 让优化器放弃 NL。
还有个容易被忽略的点:绑定变量窥探失效导致硬解析频繁,每次解析都触发 shared pool latch + library cache latch,间接推高 CBC latch 等待 —— 所以先确保 cursor_sharing = exact 且 SQL 文本稳定。
- 定位 NL 驱动热点:
SELECT sql_id, plan_hash_value FROM v$sql_plan WHERE operation = 'NESTED LOOPS' AND options = 'JOIN' AND object_name = '<hot_table>' - 临时改写(测试用):
SELECT /*+ use_hash(a,b) */ * FROM t1 a, t2 b WHERE a.id = b.t1_id - 检查绑定变量稳定性:
SELECT sql_id, child_number, is_bind_sensitive, is_bind_aware FROM v$sql WHERE sql_text LIKE '%<your_sql_pattern>%' AND rownum < 10
真正难的不是识别热块,而是区分它是业务逻辑注定的(比如订单中心查最新 10 条),还是执行路径扭曲导致的(比如统计信息陈旧引发 NL 回表风暴)。后者调优见效快,前者就得接受它,并把关注点转向 latch 拆分、IO 分散或应用层缓存。










