ITPub博客

首页 > 数据库 > Oracle > 统计信息备份和还原

统计信息备份和还原

原创 Oracle 作者:a960549548 时间:2026-06-18 10:25:51 0 删除 编辑

先说为什么这事值得单独拎出来讲。

 

干 DBA 的都遇到过这么个场景:某张表数据量变了,或者刚做完大批量数据导入, CBO 的统计信息该重新收集了。 dbms_stats.gather_table_stats 一敲下去,心里就开始打鼓——这次收集完,执行计划会不会变?变了是变好还是变坏?

 

最怕的是那种 " 收集完反而变慢 " 的情况。统计信息一旦更新,原来的计划就成了过眼云烟,想回到 " 昨天那个能跑的计划 " 却发现自己连昨天的统计信息长什么样都没存。这时候如果能拿出一套之前备份好的统计信息导回去,问题马上就解决了。

 

所以统计信息备份这事,本质上是给优化器买的一份保险,应用场景不止一个:

 

重新收集统计信息前,先存一份,万一计划劣化好回滚;

生产出了执行计划问题,想把生产的统计信息搬到测试环境复现;

数据库升级、迁移前,留个底;

SYSTEM 统计信息(机器的 CPU/IO 特征)调整前的快照。

Oracle 官方有篇文档专门讲这个, MOS Doc ID 464939.1 。下面把整套流程捋一遍。

 

第一步:建一张统计信息存放表

统计信息不是凭空存的,得先有个 " 容器 " 。 dbms_stats.create_stat_table 就是干这个的,它会在指定 schema 下建一张普通表,专门用来装导出的统计信息。

 

注意一个细节:这张表要建在谁的 schema 下? 官方建议建在被统计对象的所有者( OWNER ) schema 下,也就是那张表的 owner 。原因是后面 export_table_stats 默认按当前用户去匹配对象,建对地方能省很多权限上的麻烦。

 

 exec dbms_stats.create_stat_table(user,'STAT_TIMESTAMP');


这里 STAT_TIMESTAMP 是你给这张存放表起的名字。建议带上时间戳或者用途,比如 STAT_20260606 、 STAT_BEFORE_UPGRADE ,别图省事叫 STAT_TABLE ——过几个月回头看,根本想不起来这是哪次的备份。命名是个习惯问题,但在这个场景下真的能救命。

 

第二步:把统计信息导进去

单张表或多张表

用 export_table_stats ,每张表跑一次:

sql

-- 表 1

exec dbms_stats.export_table_stats(user,'<TABLE_NAME>',NULL,'STAT_TIMESTAMP');

 

-- 表 2

exec dbms_stats.export_table_stats(user,'<TABLE_NAME_2>',NULL,'STAT_TIMESTAMP');

第三个参数是 partname (分区名),整表导出时传 NULL ;第四个参数就是上面建好的存放表名。

 

整个 schema

如果不想一张张挑,直接整个 schema 一锅端:

 

sql

exec dbms_stats.export_schema_stats(user,'STAT_TIMESTAMP');

这个我比较推荐在升级、迁移前用 ——省得漏。生产环境跑大变更之前,整个业务 schema 导一份,是个性价比很高的动作。

 

别忘了 SYSTEM 统计信息

这里有个容易漏的点: SYSTEM 统计信息( System Statistics )和表的统计信息不是一回事。它描述的是这台机器的 " 身体素质 " —— CPU 速度、单块 / 多块读的耗时、 MBRC 等等。 CBO 算成本时离不开它,但它存在 sys.aux_stats$ 里,跟表统计信息分开管。

 

先查一下有没有自定义的 system stats :

sql
select sname, pname, pval1
  from sys.aux_stats$
 where pval1 is not null;

如果只返回 STATUS 、 DSTART 、 DSTOP 这类,没有 CPUSPEED 、 SREADTIM 、 MREADTIM 、 MBRC 这些实质性的值,说明你用的是默认的 NOWORKLOAD 模式,没什么好备份的。但如果跑过 gather_system_stats ,建议单独存一份:

 

sql

-- 存放表前面已经建过了,这里复用

exec dbms_stats.export_system_stats('STAT_TIMESTAMP');

顺带提一句, SYSTEM 统计信息一旦改变,影响的是整个库所有 SQL 的成本计算,所以动它之前一定要留底,而且最好挑业务低峰——它会让大量 SQL 重新硬解析。

 

第三步:该收集就收集

备份做完了,底气就有了。这时该怎么重新收集就怎么收集,官方那篇 1226841.1 ( How To: Gather Statistics for the Cost Based Optimizer )讲得很细,这里不展开。

 

第四步:出事了,还原

万一收集完发现计划劣化,或者新统计信息有问题,就把备份导回去:

 

sql

-- 单表还原

exec dbms_stats.import_table_stats(NULL,'<TABLE_NAME>', NULL,'STAT_TIMESTAMP');
exec dbms_stats.import_table_stats(NULL,'<TABLE_NAME_2>', NULL,'STAT_TIMESTAMP');

注意这里第一个参数( ownname )写的是 NULL ,因为它会去存放表所在的 schema 找。如果备份表和目标对象不在同一个 schema 下,要把 ownname 显式写出来。

 

想确认这张存放表里到底存了哪些表的统计信息:

sql

select distinct c1 from STAT_TIMESTAMP where type='T';
type='T' 表示 table 类型。c1 字段存的就是对象名。一行行看下来,确认要还原的对象都在,再一把梭:

sql

exec dbms_stats.import_schema_stats(user,'STAT_TIMESTAMP');

几个实操上的坑

把这套流程跑顺之后,回头看几个容易翻车的地方:

 

1. 备份表本身也得管。 create_stat_table 建出来的就是一张普通用户表,会跟着 schema 一起被 exp/imp 、 expdp/impdp 带走,也会占空间。备份完最好导出成 dmp 或者记录一下位置,别哪天被人当成废弃表给删了。

 

2. 跨库搬统计信息要配套搬结构。 把生产的统计信息导到测试库复现执行计划问题时,测试库的表结构、索引、分区定义必须和生产一致,否则 import 时对不上号,统计信息导进去也是错位的。这一点我见过好几次栽跟头的。

 

3. 统计信息有 " 锁定 " 这一说。 如果目标表上设置了 LOCK_TABLE_STATS , import 会被挡掉,得先 unlock_table_stats 。复现问题或者保护关键表计划时,锁定统计信息其实是个挺有用的手段,配合备份一起用更稳。

 

4. 别把统计信息备份和统计数据本身搞混。 备份的是 CBO 看到的 " 统计画像 " ,不是表里的业务数据。它能帮你还原执行计划,但还原不了业务数据——那是 RMAN 和闪回的活。

 

5. 还原之后别忘了重新看看计划。 import 完成意味着统计信息回退了,但已经在 shared pool 里的游标不会自动作废。需要的话,刷一下相关 SQL 的游标(比如 dbms_shared_pool.purge ,或者改个 hint 触发硬解析),让 CBO 按回退后的统计信息重新生成计划。

 

写在最后

整套备份 / 还原统计信息的流程其实不复杂,核心 : create_stat_table 、 export_*_stats 、 import_*_stats ,加上一个 system stats 的特例。难点不在语法,在于养成习惯——在每一个 " 动统计信息 " 的动作之前,先花两分钟留个底。

 

统计信息引发的执行计划问题,排查起来往往很折磨人,因为它不像报错那样直白,而是 " 昨天好好的,今天就慢了 " 。手里攥着一份干净的备份,等于把 " 昨天 " 这个状态固化了下来,进可复现、退可回滚。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70013625/viewspace-3174004/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
Oracle ACE,MySQL 8.0 OCP,PMP,拥有OceanBase OBCP,TiDB PCTP,GoldenDB GDCP等多项认证,ITPUB认证专家,微信公众号:DBA之友。

注册时间:2022-02-08

  • 博文量
    132
  • 访问量
    171523