ITPub博客

首页 > 数据库 > Oracle > 利用 DBMS_STATS 一键回退统计信息

利用 DBMS_STATS 一键回退统计信息

原创 Oracle 作者:a960549548 时间:2026-11-11 16:26:56 0 删除 编辑

一个经典的生产事故剧本:白天业务平稳,夜里自动统计信息收集任务跑完,第二天早上核心 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

三、注意事项

  1. 回退不是根治:它可以做应急使用,新统计信息为什么收坏(采样比例、数据倾斜、直方图桶数)仍要单独排查,否则下次收集还会翻车;

  2. 历史保留期默认 31 天,超期自动清理;排查"半个月前那次收集"是可行的,指望回退一年前不可能。调整保留期用 ALTER_STATS_HISTORY_RETENTION,需要 SYSDBA 或 ANALYZE ANY DICTIONARY 加 ANALYZE ANY 权限,注意调大保留期会增加 WRI$OPTSTAT* 基表的占用;

  3. as_of_timestamp 必须落在历史可用范围内,先用 GET_STATS_HISTORY_AVAILABILITY 确认最早时间点,再用 DBA_TAB_STATS_HISTORY 挑准版本;

  4. 回退连带恢复索引与列的统计信息,但用户自定义统计(user-defined statistics)不在恢复范围内;

  5. 回退、收集一样会留下操作记录(DBA_OPTSTAT_OPERATIONS),事后审计能看到完整链路;

  6. 与其事后回退,不如事前隔离:12c 起 GATHER_TABLE_STATS 的 PUBLISH => FALSE 先收集成 pending 状态,验证无误再发布;或对计划敏感的表 LOCK_TABLE_STATS 冻结。回退、pending、锁定三招按场景选。

执行计划变差的原因很多,统计信息突变是最常见的一类。下次遇到夜里收集完统计信息、白天 SQL 变慢的情况,第一反应不用是慌着重收集——先查历史,能回退就先回退止血,再从容查根因 。


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

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

注册时间:2022-02-08

  • 博文量
    136
  • 访问量
    175971