必须用gv$active_session_history过滤session_state='on cpu'且结合sql_plan_hash_value定位index skip scan的真实cpu消耗,因event字段不直接记录该操作而反映等待事件,且awr历史视图因采样粒度粗、字段不完整无法准确归因。
查 index skip scan 的真实 on cpu 样本
直接在 gv$active_session_history 里过滤 session_state = 'on cpu' 且 event 包含 index skip scan 的记录,才能定位跳跃扫描的真实 cpu 消耗。别只看执行计划里写了 index skip scan 就认为它在吃 cpu——很多跳跃扫描实际卡在 i/o 或锁等待上,event 字段才是关键判据。
常见错误是只查 sql_id 对应的全部 ASH 样本,结果混入大量 db file sequential read 或 enq: TX - row lock contention,误把 I/O 瓶颈当成 CPU 问题。
- 必须加
session_state = 'ON CPU'—— 这是唯一能确认“此刻真正在 CPU 上跑”的条件 -
event字段不直接存INDEX SKIP SCAN,它显示的是当前等待事件;所以得反向查:先拿到疑似跳跃扫描的sql_id,再确认其ON CPU样本是否集中出现在该 SQL 执行期间 - RAC 环境务必用
gv$active_session_history,否则可能漏掉其他节点上正在做跳跃扫描的会话
关联 sql_plan_hash_value 判断子游标差异
同一 sql_id 下,不同 sql_plan_hash_value 可能对应完全不同的访问路径:一个走 INDEX RANGE SCAN,另一个走 INDEX SKIP SCAN。如果只统计 sql_id 级别的 CPU 样本,会把两种执行路径的成本混在一起,无法归因到跳跃扫描本身。
执行计划中出现 INDEX SKIP SCAN 并不等于每次执行都走它——优化器可能因绑定变量窥探、统计信息陈旧或隐式类型转换,在不同子游标中选择不同路径。
- 查
gv$active_session_history时,必须带上sql_plan_hash_value字段,再和v$sql_plan关联确认该 plan_hash_value 对应的操作符确实是INDEX SKIP SCAN - 注意
child_number:即使sql_plan_hash_value相同,不同child_number的实际执行开销也可能因自适应游标共享而不同 - 若发现某
sql_plan_hash_value的ON CPU样本数远高于其他子游标,且其执行计划含INDEX SKIP SCAN,基本可锁定跳跃扫描为高成本根因
对比逻辑读与 CPU 样本比值,识别低效跳跃
索引跳跃扫描的理论优势是减少逻辑读,但如果前导列基数被误估(比如统计信息过期),导致 Oracle 创建了过多逻辑子索引,反而引发大量 CPU 解析和跳转开销。此时你会看到:逻辑读没降多少,但 ON CPU 样本数飙升。
典型表现是 gv$active_session_history 中该 SQL 的 ON CPU 占比 >70%,而 v$sql 中的 buffer_gets 却未显著下降——说明 CPU 耗在索引结构遍历上,而非数据处理。
- 用
SELECT sql_id, sql_plan_hash_value, COUNT(*) cpu_samples FROM gv$active_session_history WHERE session_state = 'ON CPU' AND sample_time > SYSDATE - INTERVAL '5' MINUTE GROUP BY sql_id, sql_plan_hash_value先抓出高 CPU 样本的组合 - 再查
v$sql:SELECT sql_id, sql_plan_hash_value, buffer_gets, cpu_time, elapsed_time FROM v$sql WHERE sql_id = '&sql_id',算cpu_time / buffer_gets比值;比值异常高(比如 > 100μs/get)即提示跳跃扫描内部开销失控 - 别依赖
DBA_HIST_SQLSTAT做历史对比——AWR 快照粒度太粗(默认 60 分钟),瞬时跳跃扫描尖刺会被平滑掉
为什么 DBA_HIST_ACTIVE_SESS_HISTORY 不适合分析跳跃扫描开销
DBA_HIST_ACTIVE_SESS_HISTORY 里的 sql_plan_hash_value 和 event 字段在 AWR 快照周期内只保留一个代表值,不是每秒采样都记。这意味着:一次持续 8 秒的跳跃扫描,若恰好跨两个快照点(比如第 29 秒和第 31 秒各采一次),它可能只在其中一个快照里留下 sql_plan_hash_value,另一个快照里是空或别的 plan;更糟的是,event 字段在快照里根本不可靠——它只反映快照时刻的等待事件,对 ON CPU 状态几乎无意义。
想看昨天下午 3:17:23 那次跳跃扫描到底吃了多少 CPU?内存视图 gv$active_session_history 是唯一可信来源,但它的保留时间通常只有 1 小时左右。真要长期归档,得自己定时快照落库,而不是指望 AWR。
-
DBA_HIST_ACTIVE_SESS_HISTORY的module、action字段也只存快照时刻值,对短时模块打标(如 Spring Boot 的setModule)基本无效 - 如果必须用历史视图,至少得结合
DBA_HIST_SQL_PLAN查 plan_hash_value 是否存在,再反推当时是否有跳跃扫描路径——但这只能验证“有没有”,不能回答“吃了多少 CPU” - 最易被忽略的一点:
gv$active_session_history中的sample_time是 TIMESTAMP WITH TIME ZONE 类型,查询时若用TO_DATE强转,可能因时区丢失精度,建议统一用SAMPLE_TIME > SYSDATE - INTERVAL '5' MINUTE











