统计信息过时、granularity参数选错、直接路径读切换、全局索引未覆盖分区键是导致分区表全表扫描的四大主因,需协同排查优化。

统计信息过时直接触发全表扫描
分区表查询走全表扫描,八成不是SQL写得差,而是优化器手里没准确的“地图”——统计信息过时或缺失。当 NUM_ROWS、AVG_ROW_LEN、DENSITY 等关键字段严重偏离真实值,CBO 会误判索引访问代价远高于全表扫描,尤其在 WHERE 条件选择性高但统计信息显示“几乎全表命中”时,它宁可扫一遍也不走索引。
典型表现:EXPLAIN PLAN 显示 TABLE ACCESS FULL,而谓词里明明有高选择性字段;DBA_TAB_STATISTICS 中 LAST_ANALYZED 时间远早于数据变更时间;分区级行数与全局行数明显对不上(比如新增百万行后 GLOBAL_STATS = 'NO')。
granularity 参数选错导致分区裁剪失效
用 DBMS_STATS.GATHER_TABLE_STATS 收集统计信息时,granularity 参数决定“谁被更新、谁被忽略”。选错就等于只修了半张地图:
-
granularity => 'PARTITION':只更新指定分区,GLOBAL_STATS保持不变 → 查询跨多个分区时,优化器因全局统计不准,不敢裁剪,退化为全表扫描 -
granularity => 'GLOBAL':只更新全局统计,各分区统计仍是旧的 → 分区裁剪可能误判某分区为空或极小,跳过本该访问的分区,或反向扩大扫描范围 -
granularity => 'AUTO'或'ALL'才能同步更新全局+所有分区统计,但代价是资源消耗大,且若未启用INCREMENTAL模式,每次都是全量重扫
实操建议:新增/修改分区后,优先用 granularity => 'AUTO';若表极大,改用 INCREMENTAL 模式 + granularity => 'PARTITION' 组合,避免重复扫描未变分区。
收集统计信息本身引发直接路径读(DPR)
这不是“计划错了”,而是“物理读行为变了”:统计信息收集后,Oracle 可能将原本走缓存的全表扫描,切换为 direct path read —— 绕过 Buffer Cache,直读磁盘。这在大表上反而更慢,尤其当 SQL 多次扫描同一张表(如多表 JOIN 中反复访问),db file scattered read 等待飙升。
触发条件包括:
- 表大小超过隐含参数
_small_table_threshold(单位:数据块) - 表在 Buffer Cache 中的缓存占比
- 脏块率 > 25%
注意:这个变化和执行计划无关,PLAN_HASH_VALUE 没变,但实际 I/O 路径已切换。可通过 v$session_event 查看 direct path read 等待是否突增来确认。
全局索引未覆盖分区键导致索引失效
即使统计信息最新,若查询按分区键过滤(如 WHERE part_key = 'P2024'),但全局索引建在非分区键字段(如 CREATE INDEX idx_global ON t(col_a) GLOBAL),优化器无法利用分区裁剪,索引条目分散在所有分区中,B树遍历成本不亚于全表扫描,CBO 直接弃用。
验证方法:
- 查
DBA_PART_INDEXES确认索引类型是否为GLOBAL - 查
DBA_IND_COLUMNS看索引列是否包含分区键 - 执行
EXPLAIN PLAN后,观察Pstart/Pstop是否为具体分区号(如8、8),还是KEY或1–MAX
修复方向:要么重建全局索引,把分区键加进索引列(需权衡 DML 性能);要么改用本地索引(LOCAL),天然支持分区裁剪。
真正棘手的是统计信息、索引结构、分区设计三者耦合——改一个参数可能暴露另一个隐藏缺陷。别只盯着 GATHER_TABLE_STATS 命令本身,先确认分区键是否入索引、INCREMENTAL 是否开启、_small_table_threshold 是否合理,再动手收集。


















