AWR报告本身并不会记录索引被调用的次数,它只提供段级别的聚合数据。要判断某个索引是否真正被高效利用,必须交叉对比segments by logical reads、segments by physical reads和segments by buffer busy waits这三张视图,再深入钻取到dba_hist_seg_stat,并过滤出object_type = 'index'的记录,否则你看到的几乎全是干扰信息。

如何准确判断索引是否被高频扫描
直接翻阅AWR报告中的Segment Statistics表格并没有实际意义——它默认仅展示top 5,而且不区分对象类型。真正的关键信息隐藏在基表里:
- 查询
dba_hist_seg_stat,添加条件statistic_name IN ('physical reads', 'logical reads'),务必不要遗漏logical reads,这是判断的核心 - 通过
dataobj#匹配object_id来JOIN dba_objects(分区索引必须采用这种连接方式,否则无法查到子分区,一查就是空结果) - 筛选
OBJECT_TYPE = 'INDEX'并且OWNER NOT IN ('SYS','SYSTEM','XDB'),排除系统对象,否则噪声会淹没真实信号 - 时间窗口要尽量收窄:
snap_id BETWEEN 1234 AND 1235,避免跨天聚合,否则偶发性扫描会被放大为常态,数据看起来吓人,实际是假象
为什么物理读高并不代表索引设计错误
某个索引的physical reads偏高,可能是主键等值查询的正常表现,这合情合理。但如果它的physical reads远高于对应基表,就需要格外警惕:
- 先分析访问模式:
access_predicates是否为空?空则说明SQL谓词根本没有走索引(例如WHERE UPPER(name) = 'ABC'这种写法,函数包裹了列,索引基本失效) - 检查
clustering_factor:如果该值接近表行数,意味着数据的物理分布与索引顺序严重偏离,范围扫描的代价会急剧上升,稍不注意就会变成全表扫描 - 确认绑定变量值是否导致执行计划倾斜——某次执行使用了极低选择性的值走了索引,后续被缓存复用,而大多数情况其实不该走,这正是典型的计划不稳定问题
physical reads direct高的索引通常并非被传统扫描,而是被并行DML或大查询直接读取,这种读操作不进buffer cache,也不会体现在logical reads中,很容易误判
AWR精度不足,必须借助ASH填补盲区
Segments by Logical Reads无法区分“一次高效的索引唯一扫描”和“一万次低效的范围扫描加回表”,更掩盖不了执行计划变更的影响。仅看聚合数据,如同雾里看花:
- 关联
ash.sql_id→dba_hist_sql_plan→plan_table_output,过滤operation包含INDEX的行,这样就可以精准定位索引究竟被哪个SQL、以何种方式使用 - 重点查看
access_predicates和filter_predicates是否与SQL谓词匹配,尤其要注意函数索引是否真正被命中——很多函数索引建了却用不上,白白浪费存储空间 - 如果
Buffer Busy Waits很高,不一定都是索引热块争用——反向键索引在高并发插入时也会触发叶块争用,根源在于写入模式,而不是查询逻辑
最容易忽略的一点是:AWR中看到的索引读数,可能来自全表扫描后的回表操作,而不是索引本身被驱动。没有通过ASH下钻确认执行计划分支,单看Segment Statistics就是在雾里看花,得出的结论基本靠猜。
