执行 SPLIT PARTITION 后统计信息不会自动更新,需显式调用 DBMS_STATS.GATHER_TABLE_STATS 并设置 granularity=>'ALL' 等参数,否则 NUM_ROWS 为 0 或陈旧,导致优化器选错执行计划。
执行 alter table ... split partition 后,统计信息不会自动更新,dba_tab_statistics.num_rows 和分区级行数(dba_tab_partitions.num_rows)常显示为 0 或陈旧值,导致优化器选错执行计划——这不是 bug,是 oracle 的设计行为。
为什么 SPLIT 后统计信息不准
Oracle 的 SPLIT 是 DDL 操作,不触发自动统计信息收集;它只移动数据(或复制元数据),但不触碰统计视图。尤其当源分区为空、或新分区刚创建时,NUM_ROWS 仍为 0,即使实际已插入数据。更隐蔽的是:DBMS_STATS.GATHER_TABLE_STATS 默认跳过空分区(除非显式指定 granularity => 'ALL'),而用户常误以为 cascade=true 就够了。
必须显式调用 DBMS_STATS.GATHER_TABLE_STATS 并设对 granularity
仅执行 cascade => true 不足以覆盖所有新分区,尤其当表有本地索引时,索引分区统计也可能滞后。关键参数组合如下:
-
granularity => 'ALL':强制扫描表+所有分区+所有子分区(含新 split 出的分区) -
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE:避免采样偏差,尤其对倾斜数据 -
method_opt => 'FOR ALL COLUMNS SIZE AUTO':让直方图按需生成,防止谓词选择率误判 -
no_invalidate => FALSE:确保游标失效,避免旧执行计划缓存干扰
示例:
EXEC DBMS_STATS.GATHER_TABLE_STATS( ownname => 'SCHEMA_NAME', tabname => 'TABLE_NAME', granularity => 'ALL', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, method_opt => 'FOR ALL COLUMNS SIZE AUTO', cascade => TRUE, no_invalidate => FALSE );
检查是否真生效:别只看 NUM_ROWS
执行完收集后,不能只查 DBA_TAB_STATISTICS.NUM_ROWS,它反映的是整个表汇总值。真正影响分区裁剪的是 DBA_TAB_PARTITIONS.NUM_ROWS 和 DBA_IND_PARTITIONS.NUM_ROWS(对本地索引)。验证命令:
SELECT partition_name, num_rows, last_analyzed FROM dba_tab_partitions WHERE table_name = 'TABLE_NAME' AND table_owner = 'SCHEMA_NAME' ORDER BY partition_position;
若新分区(如 PART_202311)的 NUM_ROWS 仍为 0 或 LAST_ANALYZED 时间早于 split 时间,说明收集未覆盖到该分区。
19c 特别注意:GLOBAL 索引分裂后需单独处理
如果表上有全局索引(非分区索引或 GLOBAL 分区索引),SPLIT 会导致其状态变为 UNUSABLE,此时即使表统计准确,优化器也可能因索引不可用而退化为全表扫描。必须先重建索引,再收集统计:
- 查索引状态:
SELECT index_name, status FROM dba_indexes WHERE table_name = 'TABLE_NAME' - 重建不可用索引:
ALTER INDEX idx_name REBUILD ONLINE(或REBUILD PARTITION对 GLOBAL 分区索引) - 再运行上面的
GATHER_TABLE_STATS,且cascade => TRUE才能同步索引统计
漏掉重建这步,DBMS_STATS 会跳过不可用索引,后续查询可能绕过索引,性能骤降。
最易被忽略的点:split 后的新分区名可能不在你预期的命名规则里(比如 P_MAX 分裂出的分区名由 Oracle 自动生成),查 DBA_TAB_PARTITIONS 确认真实分区名,再核对统计时间戳和行数——名字错了,检查就白做了。


















