统计信息不准确必然误导优化器:exchange partition不触发统计更新,需显式执行dbms_stats.gather_table_stats并指定granularity=>'all'或'global and partition',同时重建unusable索引,否则即使统计正确,查询仍会因索引不可用而退化为全表扫描。

统计信息不准确不是“可能慢”,而是“一定误导优化器”——只要 DBMS_STATS.GATHER_TABLE_STATS 没跑对粒度、没覆盖关键分区、没处理索引状态,执行计划就大概率走错。
为什么刚交换完分区,SQL就从走索引变成全表扫描?
根本原因不是数据变了,是统计信息断层了。EXCHANGE PARTITION 操作本身完全不触发任何统计信息更新,哪怕你表启用了 INCREMENTAL => TRUE。Oracle 不会自动刷新 synopsis,也不会重算全局 NUM_ROWS 或 AVG_ROW_LEN。
- 交换后源表和目标表的
LAST_ANALYZED时间戳不变,CBO 仍用旧基数估算 - 如果只对新分区单独收集(
granularity => 'PARTITION'),全局统计信息仍是过期的,CBO 在判断是否使用分区裁剪时可能误判 - 本地索引交换后变
UNUSABLE,即使你收集了统计信息,查询也会因索引不可用而强制退化
该用哪个 granularity 参数才真正生效?
别信 AUTO —— 它在分区表上行为不稳定,尤其跨版本(12c/19c)表现不一致。生产环境必须显式指定:
-
granularity => 'ALL':最稳妥,同时更新全局 + 所有分区 + 所有子分区统计信息;适合交换后首次收集或低峰期全量刷新 -
granularity => 'GLOBAL AND PARTITION':比ALL轻量,跳过子分区,但能保证全局和每个一级分区都有准确实时值;日常维护推荐 -
granularity => 'PARTITION':仅更新指定分区,必须配合partname => 'P_202407'使用;单独用它会导致全局统计信息滞后,CBO 可能拒绝分区裁剪
错误示例:granularity => 'PARTITION' 却没传 partname,语句静默成功但什么都没做。
索引状态不修复,统计信息再准也没用
统计信息和索引可用性是两件事。常见陷阱是跑了 cascade => TRUE 就以为万事大吉 —— 它只收集索引统计,不重建 UNUSABLE 索引。
- 本地索引交换后失效:查
USER_IND_PARTITIONS.STATUS,对每个UNUSABLE分区执行ALTER INDEX idx_name REBUILD PARTITION p_name - 全局索引交换后失效:查
USER_INDEXES.STATUS,必须先ALTER INDEX idx_name UNUSABLE(毫秒级),再REBUILD(避免锁冲突) - 重建后务必验证:不只是看
STATUS = 'USABLE',还要EXPLAIN PLAN真实 SQL,确认是否走了预期索引路径
增量统计开启后,为什么还是得手动收集?
INCREMENTAL => TRUE 的作用范围很窄:它只在 INSERT/UPDATE/DELETE 后,由 Oracle 自动维护 SYSAUX 中的 synopsis,并用于后续 GLOBAL 统计推算。但它完全不响应 DDL 操作,包括 EXCHANGE、DROP、SPLIT。
- 交换后必须显式调用
DBMS_STATS.GATHER_TABLE_STATS,否则GLOBAL统计永远停留在交换前快照 - 若依赖
INCREMENTAL,需额外加OPTIONS => 'GATHER STALE'并确保STALE_PERCENT设置合理(默认10%,但大表建议调低) - 检查是否真启用:
SELECT incremental FROM user_tab_partitions WHERE table_name = 'YOUR_TABLE',返回YES才有效
最易被忽略的一点:即使所有操作都按规范执行,DBMS_STATS 默认不收集列级直方图(method_opt 未指定时)。而分区键列若存在数据倾斜(如某些分区数据量是其他分区的百倍),没有直方图,CBO 基数估算仍会严重失真 —— 这类问题只能靠 method_opt => 'FOR COLUMNS SIZE AUTO' 显式触发。











