统计信息不是可有可无的元数据,而是成本优化器制定执行计划的依据。一条SQL从解析到生成执行计划,Oracle需要知道表有多少行、列有多少不同值、数据分布是否倾斜、索引聚簇因子如何。这些信息一旦与真实数据偏差过大,优化器就会算错基数,例如把一个应该走索引的查询估算成返回几百万行,从而选择全表扫描或错误的表连接顺序。收集统计信息的过程看似简单,实际涉及采样率、直方图、分区粒度、并发度等多个参数,策略不当会引入新的性能问题。

下面结合Oracle 19c及之后的版本,从原理、参数和实战脚本三个层面说明如何建立可维护的统计信息收集与更新体系。
一、统计信息如何影响执行计划
Oracle优化器依赖DBA_TAB_STATISTICS、DBA_TAB_COLUMNS、DBA_INDEXES等数据字典中的统计值来计算每个执行步骤的基数。表行数NUM_ROWS是最基础的输入,列的不同值数量NUM_DISTINCT决定等值条件的选择率,直方图描述数据倾斜,聚簇因子CLUSTERING_FACTOR影响索引扫描成本。如果某个订单表每天新增50万行,但统计信息还停留在三天前,优化器可能认为全表只有200万行,实际已经350万行,原本合适的索引范围扫描会被错误估算,进而生成全表扫描计划。
除了数据量变化,索引统计信息也一样关键。某个复合索引的BLEVEL、LEAF_BLOCKS如果偏差较大,索引扫描成本会被高估或低估。曾经有一个案例,一张流水表每天归档后物理块大量收缩,但统计信息没有更新,导致优化器一直认为索引高度为3、叶块几万个,实际上索引已经缩小80%,结果原本适合索引的查询持续走全表扫描。由此可见,统计信息收集和更新不是每天执行一次就高枕无忧,需要根据表的数据变化速率和分区特性来设计。
二、DBMS_STATS收集策略与关键参数
Oracle提供DBMS_STATS.GATHER_TABLE_STATS过程收集表、列、索引统计信息,也支持按schema、database级别收集。实际生产环境很少使用默认值无脑收集,而是根据表规模调整参数。以下是几个影响较大的参数:
estimate_percent:采样百分比,默认AUTO_SAMPLE_SIZE,Oracle会通过算法自动确定采样大小,通常能兼顾精度与时间。对于超大表如果希望缩短收集窗口,可以设置较小值,但基数估算可能不稳定。method_opt:控制列统计信息和直方图收集范围。常用FOR ALL COLUMNS SIZE AUTO或FOR ALL COLUMNS SIZE REPEAT。SIZE AUTO由Oracle判断哪些列需要直方图,SIZE REPEAT只收集已有直方图的列,适合已经稳定的系统,避免每次重新分析所有列。cascade:是否同时收集索引统计信息。设置为TRUE时Oracle会收集或更新索引统计信息,也可以单独执行GATHER_INDEX_STATS。通常建议开启,除非索引数量特别多且索引统计信息依然准确。degree:并行度,大表收集统计信息时可以设置并行加快速度,但要考虑与业务高峰的资源竞争。no_invalidate:控制收集后依赖游标是否立即失效。设置为FALSE会立即让相关SQL硬解析,可能引起短时间解析风暴;设置为TRUE则保留已有游标,新解析使用新统计信息,适合平稳过渡。
下面是一个实际生产常用的收集脚本,针对大表采用自动采样、并行4、收集所有列自动直方图、级联索引、不立即失效游标:
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'SCOTT',
tabname => 'ORDER_DETAIL',
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
method_opt => 'FOR ALL COLUMNS SIZE AUTO',
cascade => TRUE,
degree => 4,
no_invalidate => TRUE
);
END;
/
需要提醒的是,method_opt写成FOR ALL COLUMNS SIZE AUTO时,Oracle会为存在数据倾斜的列创建高度均衡或频率直方图,但这会增加额外分析开销。对于稳定的核心表,第二版可以用FOR ALL COLUMNS SIZE REPEAT,只刷新已有直方图,减少字典表变化。
除单表收集外,也可以使用DBMS_STATS.GATHER_SCHEMA_STATS和DBMS_STATS.GATHER_DATABASE_STATS批量收集,但这两个过程在大批量任务下容易漏掉某些失效对象,且耗时很长。推荐先收集变化较大的业务表,再收集schema内其余小表。
三、自动维护任务与更新策略
Oracle默认会开启自动统计信息收集任务,即维护窗口中的ORA$AT_OS_OPT_SY_相关作业。它会在夜间调用DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC,优先处理统计信息缺失或过期的对象。这个任务不是一次收集所有表,而是根据变化量排序。不足在于:如果表在日间批量装载后马上有大量查询,统计信息可能来不及等到夜间窗口,白天执行计划已经走偏。
判断统计信息是否过期可以查询DBA_TAB_STATISTICS中的STALE_STATS字段,值为YES表示该表统计信息已过期。下面脚本找出当前用户下过期且行数较大的表:
SELECT table_name,
num_rows,
stale_stats,
last_analyzed
FROM dba_tab_statistics
WHERE owner = 'SCOTT'
AND stale_stats = 'YES'
AND num_rows > 1000000
ORDER BY num_rows DESC;
对于日增数据较大的表,可以设置表级首选项STALE_PERCENT,默认10%,即变化超过10%才标记为过期。如果某表一天变化30%,希望更早触发收集,可以将该值调低到5%。使用DBMS_STATS.SET_TABLE_PREFS设置:
BEGIN
DBMS_STATS.SET_TABLE_PREFS(
ownname => 'SCOTT',
tabname => 'ORDER_DETAIL',
pname => 'STALE_PERCENT',
pvalue => '5'
);
END;
/
分区表适合采用增量统计信息收集,Oracle只对变化的分区收集全局统计信息,而不是扫描整张表,能显著降低大表收集时间。首先需要开启增量统计信息,然后后续收集时指定granularity=>'AUTO'。开启方式:
BEGIN
DBMS_STATS.SET_TABLE_PREFS(
ownname => 'SCOTT',
tabname => 'ORDER_PART',
pname => 'INCREMENTAL',
pvalue => 'TRUE'
);
END;
/
随后执行GATHER_TABLE_STATS时,Oracle会生成synopsis信息并只扫描有变化的分区,全局统计信息由分区级聚合而成。需要注意增量收集在分区交换、截断分区后可能产生不准确的全局统计,此时需要对单个分区手动收集或重建统计信息。
四、锁定统计信息与特殊场景
有些表的数据长期不变,或者经过人工调整后执行计划已经非常稳定,这时可以锁定统计信息,防止自动任务或人为误操作将其覆盖。使用DBMS_STATS.LOCK_TABLE_STATS锁定表级统计信息,使用LOCK_PARTITION_STATS锁定分区级统计信息。锁定的表即使执行收集过程也会被忽略。
下面脚本锁定一张维表,避免统计信息被自动任务改变:
BEGIN
DBMS_STATS.LOCK_TABLE_STATS(
ownname => 'SCOTT',
tabname => 'PRODUCT_DIM'
);
END;
/
解锁时调用UNLOCK_TABLE_STATS即可。不过要注意,锁定统计信息后如果数据量确实发生较大变化,执行计划可能长时间不变并导致性能下降,需要制定人工评审机制,例如每月检查一次锁定对象的数据变化量和最后分析时间。
柱状图是另一个容易踩坑的点。对于绑定变量窥探场景,直方图有时会误导优化器,因为绑定变量第一次解析时生成的计划可能不适用于后续所有值。若是数据分布非常倾斜且应用频繁使用绑定变量,可以考虑删除直方图或使用扩展统计信息,例如对关联列建立列组统计。删除某列直方图的脚本如下:
BEGIN
DBMS_STATS.SET_COLUMN_STATS(
ownname => 'SCOTT',
tabname => 'ORDER_DETAIL',
colname => 'STATUS',
distcnt => NULL,
density => NULL,
nullcnt => NULL,
srec => NULL,
avgclen => NULL
);
END;
/
该脚本将STATUS列的直方图清空,让优化器按均匀分布估算。对于数据倾斜且不使用绑定变量的场景,保留直方图仍是最佳选择。
收集统计信息本身也会消耗资源和产生锁等待。大表在业务高峰期间收集可能造成字典表争用或CPU升高,所以尽量安排在维护窗口,或使用degree控制并行度。如果必须在线收集,建议先对单表做测试,确认耗时和系统负载可接受。收集完成后及时检查DBA_TAB_STATISTICS中的LAST_ANALYZED和STALE_STATS是否变为NO。
最后需要说明,统计信息策略没有一套绝对标准,核心是根据表的数据增长、分区设计、业务查询模式制定不同频率和粒度。小表可以依赖自动任务每日收集,大表和分区表建议设置合理的STALE_PERCENT并开启增量收集,对稳定对象锁定统计信息。建立这些规则后,可以用脚本定期审计统计信息状态,及时发现长期未分析的过期对象,避免执行计划突然恶化。

