什么是 Index Full Scan?
想象一本按姓氏排序的电话簿。
- 你要看所有号码,且希望结果仍按姓氏排序。
- 你当然可以直接从第一页翻到最后一页,从头到尾扫一遍。
- 电话簿本身就是有序的,因此你无需再排序。
Oracle 的 索引全扫描(Index Full Scan) 就是干这件事:
- 从索引根块 → 沿最左子树 → 依次读取所有叶块。
- 使用 单块 I/O(
db file sequential read
),顺序由索引保证。 - 结果天然有序,避免 Memory/Temp 排序。
关键字:单块读、天然有序、无额外 SORT ORDER BY。
优化器为什么敢用 Index Full Scan?
官方文档给出了 3 条硬性条件,可简化为一句口诀:
“要么谓词用到索引列,要么索引包打天下且列非空,要么 ORDER BY 列非空。”
场景 | 是否满足 | 说明 |
---|---|---|
WHERE department_id = 10 | ✅ | 谓词引用索引列(不必是前导列)。 |
SELECT department_id, department_name FROM departments | ✅ | departments 表的两列全在索引 DEPT_ID_PK(主键索引包含 ROWID),且 department_id NOT NULL。 |
ORDER BY department_id | ✅ | ORDER BY 列非空。 |
只要命中其一,优化器就可能选择 INDEX FULL SCAN 而非 TABLE FULL SCAN + SORT。
执行计划长什么样?
SELECT department_id, department_name
FROM departments
ORDER BY department_id;
Id | Operation | Name | Cost |
---|---|---|---|
0 | SELECT STATEMENT | 2 | |
1 | TABLE ACCESS BY INDEX ROWID | DEPARTMENTS | 2 |
2 | INDEX FULL SCAN | DEPT_ID_PK | 1 |
- INDEX FULL SCAN 先有序地吐出 ROWID。
- TABLE ACCESS BY INDEX ROWID 根据 ROWID 回表拿
department_name
。 - 注意:没有 SORT ORDER BY 步骤,因为索引已保证顺序。
内部怎么扫?
- 从根块开始,定位到最左叶块(降序扫描则从右边开始)。
- 利用叶块的双向链表 向右水平遍历。
- 每拿到一个索引条目,回表一次(单块 I/O)。
- 直到最后一个叶块,扫描结束。
什么时候 不 选 Index Full Scan?
- 索引列允许 NULL,且查询条件为
IS NULL
(无法定位)。 - 查询列 不全是 索引列,需要回表次数太多(成本高于全表扫描)。
- 统计信息过期,CBO 误判成本。
- 需要并行扫描(Index Fast Full Scan 更适合多块 I/O)。
- 验证列非空:
SELECT nullable FROM user_tab_cols WHERE table_name='DEPARTMENTS';
- 查看索引列:
SELECT column_name FROM user_ind_columns WHERE index_name='DEPT_ID_PK';
- Hint 强制测试:
SELECT /*+ INDEX(departments dept_id_pk) */ …
观察执行计划。 - 监控排序事件:
SELECT name, value FROM v$mystat WHERE name='sorts (rows)';
若 INDEX FULL SCAN 成功,该值不再增长。
当查询需要“全量 + 有序”的结果时,让索引自己走完全程,既是 I/O 最省,也是 CPU 最优的解法。
下次看到执行计划里出现 INDEX FULL SCAN
,别再惊讶——那是 Oracle 在悄悄告诉你:“我已经替你省了一次排序。”