统计信息是优化器的大脑。信息失真,执行计划就“抽筋”。本文基于一次真实生产变更,将内部维护手册提炼成可落地的技术博客,包含:
- 变更前如何备份
- 如何优雅地收集统计信息(ANALYZE vs DBMS_STATS)
- 变更后如何快速验证与回滚
1. 变更前:三件事必须做
步骤 | 目的 | 参考脚本 |
---|---|---|
1.1 记录失效对象 | 收集后对比,防止新失效 | create table t_invalid_objects as select * from dba_objects where status<>'VALID'; |
1.2 创建统计信息备份表 | 把统计信息“倒”出来 | exec dbms_stats.create_stat_table(ownname=>'WWW',stattab=>'STAT_BAK20250816'); |
1.3 导出关键对象统计信息 | 一键回滚 | plsql<br>exec dbms_stats.export_table_stats(<br> ownname => 'OWNER',<br> tabname => 'TABLE_NAME',<br> statown => 'WWW',<br> stattab => 'STAT_BAK20250816',<br> statid => 'ID20250816');<br> |
小贴士:STAT_BAK20250816 这个表最好放在单独的统计信息表空间,防止和业务表空间耦合。
ANALYZE 已过时,DBMS_STATS 才是主流
为什么弃用 ANALYZE?
- 不支持并行度、粒度、直方图等高级参数
- 未来版本可能移除
整库轻量级收集(业务低峰)
BEGIN
dbms_stats.gather_schema_stats(
ownname => 'SCOTT',
options => 'GATHER AUTO', -- 只动“空/过期”对象
estimate_percent => dbms_stats.auto_sample_size,
method_opt => 'for all columns size repeat',
degree => 8,
granularity => 'AUTO',
cascade => TRUE,
no_invalidate => FALSE); -- 立即失效游标,立等生效
END;
/
单表深度收集(大表/核心表)
BEGIN
dbms_stats.gather_table_stats(
ownname => 'SCOTT',
tabname => 'ORDERS',
partname => 'P202508', -- 分区表可指定分区
estimate_percent => 100, -- 大表可改回 AUTO
method_opt => 'for all columns size skewonly',
degree => 16,
cascade => TRUE,
granularity => 'ALL', -- 分区+子分区+全局
no_invalidate => FALSE);
END;
/
只刷索引(索引重建后)
BEGIN
dbms_stats.gather_index_stats(
ownname => 'SCOTT',
indname => 'IDX_ORDERS_CREATED',
degree => 8);
END;
/
变更后:3 分钟完成验证
检查项 | SQL |
---|---|
新增失效对象 | select object_name,owner from dba_objects where status<>'VALID' minus select object_name,owner from t_invalid_objects; |
SQL 性能 | 对比 AWR/ASH 中 Top SQL 的 elapsed time |
执行计划 | select * from table(dbms_xplan.display_cursor(sql_id=>'xxxx')); |
如果
no_invalidate=>FALSE
会带来瞬间硬解析风暴,可配合alter system flush shared_pool;
后观察v$librarycache
的 reloads。
Q1:收集后执行计划反而变差? A:99% 是直方图“翻车”。用 method_opt=>'for all columns size 1'
删除直方图再试。
Q2:可以一边跑业务一边收集吗? A:可以,把 no_invalidate
设默认 DBMS_STATS.AUTO_INVALIDATE
,业务无感知,但 SQL 可能要等下一次执行才用上新的统计信息。
Q3:统计表占空间太大怎么办? A:定期 exec dbms_stats.purge_stats(sysdate-7);
清理历史。
备份是底线,粒度是关键,回滚是底气