首页 > 数据库 > Oracle > 利用 DBMS_STATS 一键回退统计信息
一个经典的生产事故剧本:白天业务平稳,夜里自动统计信息收集任务跑完,第二天早上核心 SQL 的执行计划全变了,一条本该走索引的 SQL 突然全表扫描,业务变慢告警电话打爆。定位到是新收的统计信息有问题,怎么办?重新收集一遍未必能收回去,手工改统计信息更是险棋。
其实 Oracle 从 10g 起就为这一幕准备了后悔药:统计信息历史。字典里的统计信息每次被修改,旧版本都会自动保存一份,DBMS_STATS.RESTORE_TABLE_STATS 可以把一张表的统计信息整体拨回到指定时刻——表、列、索引一并回退,等于把优化器看到的世界还原到计划还正常的那一天 。
自动保存 只要统计信息被修改(收集、SET、删除),旧版本就自动进历史,无需任何配置。历史数据落在 SYS.WRI$_OPTSTAT_* 一组基表里。
自动清理 历史按保留期自动清除,默认 31 天。相关子程序:
-- 查当前保留期(天)
select dbms_stats .get_stats_history_retention from dual ;
-- 查历史里最早的可用时间点,再往前无法回退
select dbms_stats .get_stats_history_availability from dual ;
-- 调整保留期(DBA,单位天;-1=永不清理,0=不再保存历史)
exec dbms_stats .alter_stats_history_retention ( 60 )
两张视图 排查时先看这两处,再决定回退到哪:
| 视图 | 内容 |
|---|---|
| DBA_TAB_STATS_HISTORY | 每张表每次统计信息保存的时间区间,选 as_of_timestamp 的依据 |
| DBA_OPTSTAT_OPERATIONS | 收集、回退、删除等操作日志 |
回退的子程序签名:
DBMS_STATS .RESTORE_TABLE_STATS (
ownname IN VARCHAR2 ,
tabname IN VARCHAR2 ,
as_of_timestamp IN TIMESTAMP WITH TIME ZONE ,
restore_cluster_index IN BOOLEAN DEFAULT FALSE ,
force IN BOOLEAN DEFAULT FALSE ,
no_invalidate IN BOOLEAN DEFAULT FALSE ) ;
as_of_timestamp 给一个时刻,Oracle 自动选用覆盖该时刻的历史版本;表及其索引、列的统计信息一并恢复。另有同族的 RESTORE_DATABASE_STATS、RESTORE_SCHEMA_STATS、RESTORE_DICTIONARY_STATS,范围递增 。
1. 准备数据并收集第一版统计信息(TEST 用户)
SQL > create table t_stats (id number , status number , pad varchar2 ( 100 )) ;
Table created.
SQL > insert into t_stats
2 select rownum , mod (rownum , 2 ) , rpad ( 'x' , 100 , 'x' )
3 from dual connect by level <= 100000 ;
100000 rows created.
SQL > commit ;
Commit complete.
SQL > exec dbms_stats .gather_table_stats ( user , 'T_STATS' )
PL/SQL procedure successfully completed.
SQL > select num_rows , to_char (last_analyzed , 'hh24:mi:ss' ) analyzed
2 from user_tables where table_name = 'T_STATS' ;
NUM_ROWS ANALYZED
---------- --------
100000 15 : 02 : 41
2. 模拟"新统计信息出了问题"
删掉九成数据再收一遍(数值上演示统计信息变了,真实场景里对应的是一次收集出了偏差):
SQL > delete from t_stats where rownum <= 90000 ;
90000 rows deleted.
SQL > commit ;
Commit complete.
SQL > exec dbms_stats .gather_table_stats ( user , 'T_STATS' )
PL/SQL procedure successfully completed.
SQL > select num_rows from user_tables where table_name = 'T_STATS' ;
NUM_ROWS
----------
10000
3. 查历史,确定回退目标
SQL > select table_name ,
2 to_char (stats_update_time , 'yyyy-mm-dd hh24:mi:ss' ) save_time
3 from user_tab_stats_history
4 where table_name = 'T_STATS'
5 order by stats_update_time ;
TABLE_NAME SAVE_TIME
-------------- -------------------
T_STATS 2026 - 11 - 11 15 : 02 : 41
T_STATS 2026 - 11 - 11 15 : 05 : 12
两行对应两版统计信息的保存时刻。目标是回到第一版收集之后、第二版之前。
4. 执行回退
SQL > exec dbms_stats .restore_table_stats ( -
> ownname => user , tabname => 'T_STATS' , -
> as_of_timestamp => to_timestamp_tz ( '2026-11-11 15:03:00' , -
> 'yyyy-mm-dd hh24:mi:ss' ))
PL/SQL procedure successfully completed.
SQL > select num_rows , to_char (last_analyzed , 'hh24:mi:ss' ) analyzed
2 from user_tables where table_name = 'T_STATS' ;
NUM_ROWS ANALYZED
---------- --------
100000 15 : 07 : 33
NUM_ROWS 回到了 100000——表的统计信息整体拨回了 15:03 时刻的版本(表上若有索引和直方图,同样一并回到该版本)。注意数据本身没有变,仍然是删剩的 1 万行,变的只是优化器看到的统计信息。真实事故里,这一步之后受影响的 SQL 重新解析,拿到的就是旧统计信息下的计划。
5. 验证操作留痕
回退动作本身也会记入历史:
SQL > select operation , to_char (start_time , 'hh24:mi:ss' ) st
2 from user_optstat_operations
3 where target = 'TEST.T_STATS'
4 order by start_time ;
OPERATION ST
--------------------------------------- --------
gather_table_stats 15 : 02 : 40
gather_table_stats 15 : 05 : 11
restore_table_stats 15 : 07 : 32
回退不是根治:它可以做应急使用,新统计信息为什么收坏(采样比例、数据倾斜、直方图桶数)仍要单独排查,否则下次收集还会翻车;
历史保留期默认 31 天,超期自动清理;排查"半个月前那次收集"是可行的,指望回退一年前不可能。调整保留期用 ALTER_STATS_HISTORY_RETENTION,需要 SYSDBA 或 ANALYZE ANY DICTIONARY 加 ANALYZE ANY 权限,注意调大保留期会增加 WRI$OPTSTAT* 基表的占用;
as_of_timestamp 必须落在历史可用范围内,先用 GET_STATS_HISTORY_AVAILABILITY 确认最早时间点,再用 DBA_TAB_STATS_HISTORY 挑准版本;
回退连带恢复索引与列的统计信息,但用户自定义统计(user-defined statistics)不在恢复范围内;
回退、收集一样会留下操作记录(DBA_OPTSTAT_OPERATIONS),事后审计能看到完整链路;
与其事后回退,不如事前隔离:12c 起 GATHER_TABLE_STATS 的 PUBLISH => FALSE 先收集成 pending 状态,验证无误再发布;或对计划敏感的表 LOCK_TABLE_STATS 冻结。回退、pending、锁定三招按场景选。
执行计划变差的原因很多,统计信息突变是最常见的一类。下次遇到夜里收集完统计信息、白天 SQL 变慢的情况,第一反应不用是慌着重收集——先查历史,能回退就先回退止血,再从容查根因
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70013625/viewspace-3188626/ ,如需转载,请注明出处,否则将追究法律责任。