上个月处理了一起 Oracle 19c RAC 的 SYSAUX 表空间 99% 告警,AWR 快照连续两天没有生成,业务方反馈数据库偶尔出现 ORA-135 报错,部分夜间批量任务也受到影响。排查到最后,基本确认是 AWR 内部数据异常:WRM$_SNAPSHOT 里的快照记录和 WRH$ 明细表对不上,手动调用 DBMS_WORKLOAD_REPOSITORY.DROP_SNAPSHOT_RANGE 清理历史快照还会触发 ORA-01555。当时评估了几种方案之后,决定直接重建 AWR。整个过程踩了不少坑,从 RAC 切单实例、清理 AWR 数据、恢复到集群状态,每一步都有讲究。今天把这套步骤按实际执行顺序整理出来,希望能帮到同样被 AWR 问题折磨的兄弟。
先说清楚一个概念:AWR 重建不是常规操作,能做的前提是问题定位准确。如果是 SYSAUX 表空间整体损坏,或者数据库出了其他更底层的问题,单纯重建 AWR 并不能解决根本故障。本文更适合你在确认“AWR 这部分的字典表、快照数据异常,且常规清理手段已经失效”的场景下参考。另外,不管是 11g、12c 还是 19c,这套思路基本通用,本次操作环境是 Oracle 19.17 的 2 节点 RAC。
1. 为什么需要重建AWR:场景与前置判断
1.1 AWR在RAC架构里的特殊地位
AWR(Automatic Workload Repository)是 Oracle 性能诊断体系的底座。默认每小时由 MMON 后台进程自动生成一次快照,把当时的等待事件、SQL 统计、会话活动、系统统计等信息写到 SYSAUX 表空间中一组以 WRM$、WRH$、WRI$ 开头的表里,保留周期默认 8 天。DBA 日常跑 awrrpt.sql 生成性能报告,看到的 Top SQL、段统计、IO 趋势都来源于这些表。
RAC 环境下的 AWR 比单实例要复杂。所有实例共享同一个数据库,SYSAUX 表空间也是共享的,每个节点的 MMON 都会往同一组 AWR 表里写数据。快照的对齐、全局数据的汇聚、节点间时钟偏差的处理,都需要内部机制去协调。一旦 AWR 表的数据出现逻辑混乱,受影响的不只是单个节点,而是整个集群的性能诊断能力。这也是为什么 RAC 里 AWR 出问题,不能只盯着那个报错的节点看,要从全局角度判断是哪个环节坏了。
1.2 触发重建的典型故障场景
结合我自己遇到的案例,以下场景最可能走到“重建”这一步:
- SYSAUX 表空间剩余空间持续告警,AWR 数据写入失败,快照无法生成。
- 查询 dba_hist_snapshot 直接报 ORA-00942、ORA-04063、ORA-00600 等错误,说明基础字典表或者相关对象已经损坏。
- 手动删除历史快照时长时间卡住,甚至报 ORA-01555,典型原因是 UNDO 太小,或者 AWR 内部清理逻辑触发了大事务回滚。
- 做过跨大版本升级或迁移之后,AWR 相关对象状态异常,大量失效对象没有恢复。
- ASH 或者某个 WRH$ 明细表异常膨胀,单个表占了十几个 GB,常规清理已经无法释放空间。
这些场景共同的特点是:常规的 AWR 维护手段(比如调整保留策略、手工 purge、删除旧快照)已经失效或不可控,只能通过重建让 AWR 回到一个干净、可用的初始状态。
1.3 动手前必须先做的三个确认
不要一看 AWR 报错就动手重建。执行之前,至少要确认三件事:
第一,问题确实出在 AWR 本身。可以先尝试 SELECT COUNT(*) FROM dba_hist_snapshot,如果报错,再看 alert 日志里有没有 AWR 相关的 ORA-00600、ORA-07445。同时确认 MMON 进程是否异常。排除掉实例级内存、ASM 磁盘空间的问题之后,再考虑重建。
第二,SYSAUX 表空间本身是否健康。AWR 的所有数据都在 SYSAUX 里,如果 SYSAUX 的数据文件有坏块或者表空间损坏,重建 AWR 也白搭。所以重建前要对 SYSAUX 做一次 RMAN 备份,并记录当前表空间大小。
第三,是否必须保留历史 AWR 数据。如果公司有合规要求,需要保留一年以上的性能数据,那重建前就要先用 awrextr.sql 把需要的历史快照导出,重建后再用 awrload.sql 导入。如果只是需要最近的报告,建议在重建前先跑一次 awrrpt.sql 把当前的问题时段报告导出来存档,避免重建后彻底丢失。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 重建前准备:备份、停机窗口与参数基线
2.1 SYSAUX表空间备份
重建 AWR 属于有损操作,虽然理论上只影响 AWR 数据,但谁也保不准执行过程中会不会误伤其他对象。我是强烈建议先做一次 RMAN 备份,确保可以随时回滚。
RMAN 备份命令很简单:
bash复制rman target /
BACKUP TABLESPACE SYSAUX FORMAT '/backup/sysaux_%U.bak' PLUS ARCHIVELOG;
如果数据库比较大,也可以只备份 SYSAUX 对应的数据文件,不强制要求全库备份。但要保证这份备份是可恢复的,至少能在同一套环境上还原 SYSAUX 这个表空间。记录好备份集的位置和日期,万一重建过程出问题,直接 restore 回来。
这里有个容易被忽略的点:如果 SYSAUX 的数据文件在 ASM 上,RMAN 备份时要注意备份目录的空间,别备份到一半把 ASM 磁盘组写满了。我那次就是没注意,备份到一半告警空间不足,只好先清理 ASM 里的旧备份。
2.2 当前AWR尺寸与快照保留策略记录
重建前记录一下 AWR 当前的“规模底数”,方便重建后对比效果,也能帮自己判断到底是不是 AWR 把 SYSAUX 撑爆的。
查询 SYSAUX 里 AWR 组件占用:
sql复制SELECT occupant_name, space_usage_kbytes / 1024 AS space_mb
FROM v$sysaux_occupants
WHERE occupant_name LIKE '%AWR%';
查询 AWR 相关表的段大小,找出占用最高的几张表:
sql复制SELECT segment_name, segment_type, SUM(bytes) / 1024 / 1024 AS size_mb
FROM dba_segments
WHERE tablespace_name = 'SYSAUX'
AND (segment_name LIKE 'WRM$%' OR segment_name LIKE 'WRH$%' OR segment_name LIKE 'WRI$%')
GROUP BY segment_name, segment_type
ORDER BY size_mb DESC;
再记录当前快照的 ID 范围和历史数据跨度:
sql复制SELECT MIN(snap_id) AS min_snap, MAX(snap_id) AS max_snap, COUNT(*) AS snap_cnt
FROM dba_hist_snapshot;
最后确认 AWR 自动快照的保留策略:
sql复制SELECT * FROM dba_hist_wr_control;
这些数据记下来,既能为重建后的验证提供对比基线,也能在回答“重建之后为什么 SYSAUX 还是那么大”的问题时,有据可查。
2.3 参数与监听状态核查
重建 AWR 的关键步骤是让 RAC 切换成单实例模式来操作,所以需要提前确认一些架构参数:
- RAC 当前使用的 spfile 位置,一般存在 ASM 磁盘组里。
- 当前
cluster_database参数值,正常应为 true。 - 监听和 SCAN 的当前状态。
- 实例名、db_unique_name 等基础信息。
可以用 crsctl status resource -t 查看集群资源状态,用 srvctl config database -d <db_unique_name> 查看数据库配置。这些信息不需要背,操作时随时查询即可,但提前确认能避免在停机窗口内手忙脚乱地查命令。
还要确认一件事:是否有应用长连接在跑。RAC 切单实例和后续重启都会中断会话,必须在业务低峰期执行,并提前通知相关方。不要只在数据库层面判断“没多少连接”,要结合应用侧的连接池配置看。很多连接池如果配置不当,数据库恢复后不会自动重连,导致业务长时间不可用。
3. RAC切单实例:核心操作与注意事项
3.1 第一步:停掉应用与连接
这一步的核心目的是让数据库处于一个无压力、无新连接的状态。我的习惯是先在应用层面确认业务已经停止,再在数据库层面停监听,避免新会话继续打进来。
逐节点停止监听:
bash复制srvctl stop listener -n node1
srvctl stop listener -n node2
如果是 19c,监听资源名一般就是 listener,可以先用 crsctl status resource -t 确认。停完监听后,再检查当前数据库活跃会话数,确认没有重要事务在跑了:
sql复制SELECT inst_id, COUNT(*)
FROM gv$session
WHERE status = 'ACTIVE' AND username IS NOT NULL
GROUP BY inst_id;
有活跃会话就再等等,或者和相关业务方确认能否终止。不要直接 kill 会话,除非已经明确获得授权。这个环节看起来简单,但做不好后面所有操作都会很被动。
3.2 修改cluster_database参数
关键操作来了。在 RAC 模式下,先不要急着把实例停掉,而是先在其中一个实例上修改 spfile 参数,把 cluster_database 改为 false:
sql复制sqlplus / as sysdba
ALTER SYSTEM SET cluster_database=FALSE SCOPE=spfile SID='*';
为什么要用 SID=''?因为 RAC 的每个实例都有自己的内存参数视图,如果不加 SID='',默认只修改当前实例的初始化参数,其他实例启动时仍然会读到旧的 true 值,根本达不到切单实例的目的。
修改完毕之后,用 SHOW PARAMETER cluster_database 确认一下当前内存里的值。注意这里查到的可能仍然是 true,因为 SCOPE=spfile 只改了 spfile,当前实例的内存在重启之前不会变。所以不用慌,继续往下走。
3.3 关闭所有实例并单实例启动
参数改好之后,停掉整个数据库:
bash复制srvctl stop database -d <db_unique_name>
等数据库完全停止后,确认一下没有残留的 oracle 进程:
bash复制ps -ef | grep pmon
正常情况下每个节点应该看不到任何 pmon 进程了。确认无误后,选择一个维护节点(比如 node1),用 sqlplus 直接启动单实例:
bash复制sqlplus / as sysdba
STARTUP;
此时因为 spfile 里 cluster_database=false,实例会以单实例模式打开。用下面几个命令确认状态:
sql复制SELECT instance_name, status FROM v$instance;
SHOW PARAMETER cluster_database;
SHOW PARAMETER db_name;
如果看到 STATUS=OPEN,cluster_database 为 FALSE,说明单实例模式已经就绪。RAC 环境里这一步虽然不难,但要注意:ASM 实例需要保持运行,因为数据文件、spfile 可能都在 ASM 上。只要 GI 栈正常,ASM 会自动处于运行状态。
4. AWR对象清理与重建
4.1 确认AWR当前状态
进入单实例模式之后,不要急着清数据,先把受损情况摸清楚。
查询 AWR 相关对象的状态:
sql复制SELECT object_name, object_type, status
FROM dba_objects
WHERE owner = 'SYS'
AND (object_name LIKE 'WRM$%' OR object_name LIKE 'WRH$%' OR object_name LIKE 'WRI$%')
ORDER BY status, object_name;
重点关注 status 为 INVALID 的对象。再尝试查询最核心的快照表:
sql复制SELECT COUNT(*) FROM dba_hist_snapshot;
如果这里直接报错,说明问题确实已经影响到底层对象了。同时把 alert 日志打开,看看最近有没有 MMON 相关的 ORA-00600 错误。
不要跳过这一步。确认范围能帮你决定后面是只做“清数据”还是需要“重建对象”。我的经验是,超过一半的 AWR 问题只是数据不一致,字典对象本身没有坏,清空数据就能恢复;只有对象真的失效时才需要走到 drop 重建那一步。
4.2 停止自动收集
清理数据之前,先暂停 AWR 的自动收集,防止清理过程中新的数据写进来,造成二次混乱。直接在单实例模式下执行:
sql复制EXEC DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS(interval => 0);
interval => 0 表示停掉自动快照。顺手也把自动清理任务停掉:
sql复制BEGIN
DBMS_AUTO_TASK_ADMIN.DISABLE(
client_name => 'auto optimizer stats collection',
operation => NULL,
window_name => NULL
);
END;
/
这一步不是必须,但如果环境里开着自动统计信息收集,清理 AWR 表之后紧接着跑统计信息收集,可能会导致一些表长时间被锁。我的习惯是把自动任务先停掉,重建完再恢复,图个清净。
4.3 清空/删除AWR数据
这是整个过程中最需要谨慎的一步。根据 4.1 的确认结果,选择不同的清理方式。
如果确认对象本身都存在,只是数据异常,就用“清空数据”的方式,保留对象结构。优先清空 WRH$ 开头的明细表,因为数据量最大、占空间最多:
sql复制BEGIN
FOR c IN (
SELECT table_name
FROM dba_tables
WHERE owner = 'SYS'
AND table_name LIKE 'WRH$%'
AND table_name NOT LIKE 'WRH$_%_TEMP'
ORDER BY table_name
) LOOP
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE SYS.' || c.table_name;
DBMS_OUTPUT.PUT_LINE('Truncated: ' || c.table_name);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Failed: ' || c.table_name || ' - ' || SQLERRM);
END;
END LOOP;
END;
/
TRUNCATE 是高水位重置操作,执行后段大小会回落到初始分配值,空间能真正释放出来。但是要注意,如果 WRH$ 表之间存在外键约束,TRUNCATE 可能会报错。这种情况下需要先找到父子表关系,先清子表再清父表。不过 AWR 的这一组表里,WRH$ 表之间一般没有强外键,风险不大。
清完 WRH$ 之后,再清 WRM$ 相关的元数据表,包括快照记录、基线、SQL 文本等:
sql复制BEGIN
FOR c IN (
SELECT table_name
FROM dba_tables
WHERE owner = 'SYS'
AND table_name IN (
'WRM$_WR_CONTROL',
'WRM$_SNAPSHOT',
'WRM$_BASELINE',
'WRM$_BASELINE_DETAILS',
'WRM$_BASELINE_TEMPLATE',
'WRM$_DATABASE_INSTANCE',
'WRM$_SEG_STAT',
'WRM$_SEG_STAT_OBJ',
'WRM$_SEG_STAT_IDL',
'WRM$_SQLSTAT',
'WRM$_SQLTEXT_SYSTEM',
'WRM$_SQLTEXT_DATABASE',
'WRM$_SQL_BIND',
'WRM$_SQL_PLAN',
'WRM$_DBLINK'
)
) LOOP
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE SYS.' || c.table_name;
DBMS_OUTPUT.PUT_LINE('Truncated: ' || c.table_name);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Failed: ' || c.table_name || ' - ' || SQLERRM);
END;
END LOOP;
END;
/
这里没有把 WRI$ 开头的所有表都清掉,因为 WRI$ 里面除了 AWR 数据,还有优化器统计信息相关的历史表,比如 WRI$_OPTSTAT_HISTHEAD_HISTORY,这些不属于 AWR 重建范畴,动它们可能会影响执行计划稳定性。除非你确认某张 WRI$ 表已经损坏,否则不要碰。
如果 4.1 里发现大量对象已经失效,或者清空数据之后对象依旧不可用,那就要考虑“重建对象”。更稳妥的做法是:生成需要删除的 AWR 对象清单,删除后从同版本正常环境中拷贝 catawr.sql 脚本来重建。
生成删除语句可以参考:
sql复制SELECT 'DROP ' || object_type || ' SYS."' || object_name || '"' ||
CASE WHEN object_type = 'TABLE' THEN ' CASCADE CONSTRAINTS' END || ';' AS drop_stmt
FROM dba_objects
WHERE owner = 'SYS'
AND (object_name LIKE 'WRM$%' OR object_name LIKE 'WRH$%' OR object_name LIKE 'WRI$%')
AND object_type IN ('TABLE', 'VIEW', 'SEQUENCE', 'SYNONYM', 'MATERIALIZED VIEW', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'FUNCTION', 'TYPE', 'TYPE BODY')
ORDER BY CASE object_type
WHEN 'TABLE' THEN 1
WHEN 'MATERIALIZED VIEW' THEN 2
WHEN 'VIEW' THEN 3
WHEN 'SEQUENCE' THEN 4
WHEN 'SYNONYM' THEN 5
WHEN 'PACKAGE' THEN 6
WHEN 'PACKAGE BODY' THEN 7
WHEN 'TYPE' THEN 8
WHEN 'TYPE BODY' THEN 9
ELSE 10
END;
把生成的语句逐条核对之后执行,然后跑 @$ORACLE_HOME/rdbms/admin/catawr.sql 重建 AWR 对象。这个脚本是 Oracle 在创建数据库时用来初始化 AWR 字典对象的,正常情况下在 11g 到 19c 的版本里都存在。
说实话,drop 重建这种方式风险很高,搞不好会把 DBMS_WORKLOAD_REPOSITORY 包、AWR 相关的视图也一起删掉,导致整个数据库的自动负载收集功能全部失效。除非你是从 Oracle 支持那里拿到了明确的修复方案,或者已经做好了充分的备份,否则我建议优先走“清空数据”的路线。
4.4 清理后收集统计信息
清空或者重建之后,AWR 相关表往往没有统计信息,或者是重建前的陈旧统计信息。这种情况下,后续 DBA 查询 AWR 视图时,优化器可能会走错误的执行计划,导致报告生成极慢。
建议对 AWR 相关表做一次统计信息收集:
sql复制BEGIN
FOR c IN (
SELECT table_name
FROM dba_tables
WHERE owner = 'SYS'
AND (table_name LIKE 'WRM$%' OR table_name LIKE 'WRH$%' OR table_name LIKE 'WRI$%')
) LOOP
BEGIN
DBMS_STATS.GATHER_TABLE_STATS('SYS', c.table_name, cascade => TRUE);
EXCEPTION
WHEN OTHERS THEN NULL;
END;
END LOOP;
END;
/
注意这一步不要用 DBMS_STATS.GATHER_SCHEMA_STATS('SYS'),SYS schema 下对象太多了,会花很长时间。只针对 AWR 相关表收集就够了。
4.5 单实例下快速验证
清理完毕,先不要着急恢复 RAC,先在单实例模式下验证 AWR 是否恢复正常。重启一次实例,让后台进程重新初始化:
sql复制SHUTDOWN IMMEDIATE;
STARTUP;
只要没有报错,说明基础对象和状态已经在恢复。然后手工生成一个快照试试:
sql复制EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT;
再查快照表:
sql复制SELECT snap_id, instance_number, TO_CHAR(begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time
FROM dba_hist_snapshot
ORDER BY snap_id DESC
FETCH FIRST 5 ROWS ONLY;
能看到新快照,说明写入链路已经通了。顺便再查一下无效对象数量:
sql复制SELECT COUNT(*) FROM dba_objects WHERE owner = 'SYS' AND status = 'INVALID';
不要有新增的无效对象。如果无效对象比之前还多,说明重建过程中有脚本没跑全,或者某些对象被误删了。
5. 恢复RAC集群并验证AWR功能
5.1 恢复cluster_database参数
单实例验证通过之后,下一步就是把数据库恢复到 RAC 模式。在单实例状态的下执行:
sql复制ALTER SYSTEM SET cluster_database=TRUE SCOPE=spfile SID='*';
SHUTDOWN IMMEDIATE;
确认实例已经完全关闭,再回到集群管理层面启动数据库。
5.2 启动所有节点
用 srvctl 启动整个数据库:
bash复制srvctl start database -d <db_unique_name>
启动完成后检查集群状态:
bash复制crsctl status resource -t
srvctl status database -d <db_unique_name>
两个实例都应该显示 ONLINE。再分别登录两个节点,确认实例状态为 OPEN:
sql复制SELECT instance_name, status FROM
