ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Oracle RMAN备份恢复与归档日志清理:生产环境安全实践指南

Oracle RMAN备份恢复与归档日志清理:生产环境安全实践指南

接手一套生产库的第一件事,我基本都会先看两样东西:备份策略长什么样,归档日志使用率到哪了。前两年排查过一套业务系统,数据库磁盘被归档日志撑满,整个实例卡在日志切换上,所有写事务全部阻塞,最后是先用 RMAN 清掉一批已经备份过的归档日志,才让系统恢复过来。RMAN 备份恢复、删除归档,听起来是 Oracle 日常运维里最常见的操作,但在生产环境里每一步都做对,绝对没那么简单。

这篇文章不打算背命令手册,重点讲底层逻辑和实操判断:RMAN 的数据块备份机制是怎么回事,归档日志在恢复链路里扮演什么角色,全备、增量、归档之间如何搭配,怎样安全地把归档日志清掉,以及真遇到数据文件损坏时,怎么把数据库完整捞回来。我会把常用命令、参数设计思路和踩过的坑一起放出来,希望对你手头的备份方案有实际参考价值。

1. 先理解 RMAN 的备份体系与归档日志机制

1.1 RMAN 和普通文件备份到底有什么区别

很多刚接触 Oracle 的运维人员,最先想到的备份方式是用 cp 或 tar 直接拷贝数据文件,这种叫用户管理备份。它看起来直观,实际上问题不少。第一,它会备份全部内容,无论数据文件里有没有实际数据,空闲块也被拷贝一遍,浪费存储和时间。第二,数据库运行时数据文件本身处于不一致状态,因为 buffer cache 里的脏数据还没刷到文件,redo log 也在不断变化。直接拷贝出来的文件要在恢复时,可能需要你自己去处理一大堆一致性校验工作,稍不留神就恢复失败。

RMAN 的逻辑完全不一样。它通过服务器会话读取数据块,找到 allocated block 也就是真正有数据的块,只备份这些块,并在备份过程中做数据块内部的逻辑校验。它生成的是自包含的备份集,里面还会记录必要的元数据,包含 SCN、数据文件号等等。恢复时 RMAN 知道该用哪个备份片,也知道备份和当前 redo log 的 sequence 之间差多少,从而自动完成恢复动作。

打个比方,手动备份就像把一屋子文件全部复印了一遍塞进仓库,备份了多少东西全靠自己心里记;RMAN 更像一套带索引的档案管理系统,它不光告诉你文件在哪,还记录什么时间点有什么版本,你只需要告诉它“恢复到某个时间”,它就能自己按目录找文件、按顺序归档重做日志。这也是为什么在数据量不小且要求可恢复性的核心库上,大家普遍选择 RMAN。

1.2 归档日志为什么是恢复的生命线

Oracle 的数据库修改流程是先写重做日志,再写数据文件。联机重做日志是循环覆盖的,一组写满之后切换下一组。如果数据库是非归档模式,切换出去的那组日志一旦被覆盖,旧内容就永久消失,数据库也就只能恢复到最近一次检查点附近。如果数据库开启了归档模式(ARCHIVELOG),日志切换时后台进程会把即将被覆盖的日志内容复制为归档日志文件,等于是给每个历史 redo 日志留下了一份完整副本。

有了这份副本,数据库才能实现介质恢复。比如周三数据文件所在的磁盘坏了,而你的全备是周日晚上做的,那么周日晚到周三损坏时刻之间的所有变更只能靠归档日志和当前在线 redo log 来重放。恢复步骤就是先把全备的物理文件恢复出来,再把备份后的归档日志从早到晚重新应用,数据库最终推进到接近故障点的状态。这也回答了一个常见问题:为什么不能随意删除归档日志。删早了删多了,恢复窗口就会缩短,遇到故障你可能只能恢复到更早的备份点,数据丢失量会成倍放大。

所以归档日志不是“越删越爽”的日志残留,它的存在本身就是 RPO(恢复点目标)的兜底。理解了这一点,再去讨论删除归档的操作,你才会明白为什么要把“是否有备份”“是否在保留策略范围内”放在删除之前先想清楚。

2. RMAN 备份实操:从环境准备到备份命令

2.1 确认数据库开启归档模式

在写备份脚本之前,必须先确认数据库处于归档模式。用 SQL 执行这条命令就可以:

SQL> archive log list; Database log mode Archive Mode Automatic archival Enabled Archive destination USE_DB_RECOVERY_FILE_DEST Oldest online log sequence 1024 Next log sequence to archive 1031 Current log sequence 1031

如果看到 No Archive Mode,需要按下面步骤把数据库切成归档模式。整个过程要重启一次实例,建议放在业务低峰或维护窗口进行:

SQL> shutdown immediate; SQL> startup mount; SQL> alter database archivelog; SQL> alter database open;

注意,开启归档模式之后,数据库会产生归档日志文件。如果你的归档目录是快速恢复区(Fast Recovery Area,FRA),那么 FRA 的空间规划直接影响能容纳多少归档文件。很多生产库走到“删除归档”这一步,其实就是因为最初 FRA 给得太小,备份全占了空间,归档一两周就把磁盘填满了。所以补课的第一步,不只是会切换归档,还要心里有数:这个库每小时/每天能产生多少归档,空间能撑多久。

2.2 全备脚本:最基础也最刚需的命令

先给出一份我实际用来做每日全备的最小脚本,它对中小型生产库基本够用:

RMAN> run { allocate channel c1 device type disk format '/backup/rman/%U'; backup database plus archivelog delete input; backup current controlfile; backup spfile; release channel c1; }

这里最值得关注的是backup database plus archivelog delete input。它的执行顺序比较巧妙:在备份数据文件之前先把当前已经生成的归档日志备份进备份集,然后开始备份数据文件,数据文件备份完成后,再备份这段时间里新生成的归档日志。也就是说,从备份开始到结束,所有归档日志都有备份,没有遗漏窗口。delete input则在成功备份这些归档日志后,把原始归档文件从磁盘删除。所以它本质上是“先备份,后清理”,这也是删除归档最安全的一种姿势,比单独执行delete archivelog all要稳得多。

有人会问,这份脚本里为什么要单独写backup current controlfile和backup spfile?RMAN 在backup database时默认也会备份控制文件和参数文件,但我习惯显式写出来。第一,恢复时可以直接引用控制文件备份,避免依赖自动行为;第二,脚本的可读性更好,换人接手时一看就明白库的骨架文件有没有被覆盖。多写两行几乎不增加成本,却能让后续的恢复过程清晰很多。

2.3 增量备份、通道配置与备份集校验

数据量超过几十 GB 后,每天全备可能不现实,备份窗口会被拉得很长。增量备份是更务实的选择。RMAN 的增量备份分 0 级和 1 级。0 级会备份一个基线,是后续增量恢复的起点;1 级只备份自上次备份以来发生变化的 block。差异式 1 级备份体积可能更小,但恢复时需要读取上一次 1 级备份及 0 级基线;累积式 1 级备份备份自 0 级以来所有变化的块,体积更大一些,但恢复时要应用的文件更少,恢复速度更快。

一个常用方案是:周日凌晨做 0 级,周一到周六做 1 级(差异式或累积式),同时每次都带上plus archivelog delete input。如果业务变化集中、恢复要求高,可以把 0 级频率提高到一天一次或两天一次。增量级别并不需要设置得多复杂,关键是和保留策略匹配得上。

通道和格式也值得聊两句。通道代表一个备份流,allocate channel c1 device type disk指定走磁盘。并发备份时多开几个通道,比如四个通道并行,可以明显压缩备份耗时,但也会提高 I/O 压力,生产上需要结合磁盘性能来定。format '/backup/rman/%U'里的%U是唯一文件名生成符,保证不同备份片不重名。如果希望备份集压缩,可改成backup as compressed backupset database ...,但压缩会占用 CPU,需要评估是否值得。

还有一个很容易被忽略的动作:备份完成后做一次validate。严格来说,validate是验证备份文件可读性和数据块完整性,不是恢复数据库。比如:

RMAN> validate backupset 33;

它会读取备份集,确认里面的数据文件块能正常读出来。这种校验成本比真正恢复低很多,建议周备份执行后跑一次,至少能提前发现备份集损坏的问题。

2.4 保留策略怎么设置

保留策略回答的问题是:RMAN 到底应该保留哪些备份和归档日志才算够用?两种常见配置是恢复窗口和冗余。

RMAN> CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 7 DAYS;

恢复窗口 7 天表示数据库必须能够恢复到过去 7 天内的任意时间点。RMAN 会根据这个窗口自动判断哪些备份集和归档日志可以标记为 obsolete,在后续执行delete obsolete时清理。

另一种是按冗余度:

RMAN> CONFIGURE RETENTION POLICY TO REDUNDANCY 2;

表示每个数据文件至少保留两份可用的备份副本。窗口更长或冗余越多,占用空间越大。具体怎么设,取决于业务允许丢失的数据量和可用磁盘空间。一个小建议:保留窗口别设成 30 天,除非你有专门的归档存储,否则备份和归档会占掉大量磁盘,清理动作追不上生成速度,最后归档堆积问题会出现得比想象快。先定业务要求,再反推空间规划,这是比较健康的做法。

3. 删除归档:安全清理的完整流程与关键坑点

3.1 归档日志为什么会越堆越多

归档堆积通常是几个问题叠加。最典型的是备份脚本里根本没有backup archivelog这一段,只做了数据文件备份,归档日志就一直堆在 FRA 里没人管。还有一种是 DBA 觉得“删除归档就是清理磁盘”,于是定期手动执行delete archivelog all,但没有关联备份策略,结果删得过于激进,真正需要恢复时反而缺文件。

业务侧的原因也不可忽视。某些批量导入、全表更新、索引重建操作会产生大量 redo,如果日志切换频率高到每几分钟切一次,一晚上生成几百份归档很正常。我记得有次接手批量任务,只跑了一小时,FRA 空间就掉了 30%,到了晚间高峰直接把磁盘写满,所有事务卡在日志切换上,数据库从“可用”变成“半挂”状态。那种场景下,第一步永远不是讨论为什么堆积,而是要先把已备份的归档安全清理掉,腾出空间让系统恢复运行。

3.2 标准操作:交叉校验、确认备份、再删除

在我的操作习惯里,手动清理归档会按固定顺序走,不直接上来就delete archivelog all。

第一步,先做交叉校验:

RMAN> crosscheck archivelog all;

crosscheck的动作是把控制文件里记录的归档日志和磁盘或带库上的实测文件做对照。有些文件可能被操作系统直接删除了,但你并不知道,如果不做这步,RMAN 会认为文件还在,后续删除时就会报找不到文件的错误。交叉校验后,找不到的归档会被标记为 expired,它不是备份也没关系,接下来可以清理掉这些状态记录:

RMAN> delete expired archivelog all;

第二步,确认这些归档日志是否已经有备份。最好的确认方式是有backup archivelog或backup database plus archivelog日志输出。也可以在 RMAN 里先查看:

RMAN> list backup of archivelog all;

我一般会看输出的范围是否覆盖到今天。如果覆盖不到,我绝不会直接删更早的归档,因为一旦后面需要介质恢复,缺口就会导致无法继续。

第三步才执行真正的删除。按天数保留的写法最有价值:

RMAN> delete noprompt archivelog until time 'sysdate-7';

这条命令先列出所有 7 天前的归档,然后自动删除。我建议执行时先不带noprompt试跑一次,让 RMAN 列出将被删除的清单,人工扫一眼再正式执行,避免误删近期重要归档。对于绝大多数业务,保留 7 天归档已经覆盖恢复窗口;如果你的全备每天都做,甚至可以只保留到上次全备时间点之前,让空间占用更少。

3.3 删除归档的三种命令和各自场景

删除归档常见写法有三种,适用场景不同:

第一种是“备份即删除”,也就是backup archivelog all delete input或backup database plus archivelog delete input。这种方法最推荐日常使用,因为它要求归档日志先进入备份集,然后才删原始文件,不会出现“备份还没做文件先没了”的尴尬。

第二种是手动按时间删除,如delete archivelog until time 'sysdate-7'。适合你已经确定归档文件本身不再需要,或者另外有工具备份了那些文件。需要注意,这里的保留天数并不是 RMAN 自动判断的,而是你人为指定的,所以要确保时间范围的判断合理。

第三种是直接清空全部归档,如delete noprompt archivelog all。这条命令风险最大,通常只在归档目录完全被塞满、业务紧急恢复运行时才使用。使用这种命令前,务必先确认你接受的数据丢失风险,并且在删完后立刻触发一次新的全备,保证后续恢复点存在。否则,一旦此刻发生数据文件损坏,能恢复的位置会非常惨淡。

3.4 三个我实测过的坑,看看你中过哪个

坑一:把delete obsolete当成归档清理命令。很多新手以为执行完delete obsolete就会释放归档空间,实际上它清理的依据是保留策略,清理对象是那些被标记为过期的备份集和备份片。归档日志本身的原始文件并不会因为delete obsolete被删除,除非它们作为备份集内容一起被判定过期。所以如果你发现归档占满磁盘,正确的思维路径是先backup archivelog ... delete input或delete archivelog,而不是纠结为什么delete obsolete没效果。

坑二:归档文件被手动 rm 后没有交叉校验直接删除。假设你在操作系统层面手动删了几个归档文件,然后执行delete archivelog all;,RMAN 会报错,提示找不到文件,看起来就像命令失败。这里的问题不是命令本身,而是控制文件记录的元数据和磁盘实际情况不一致。解决方式就是先crosscheck archivelog all;,再用delete expired archivelog all;,让 RMAN 先对账,再清理无效记录。整个过程跑通只需要几秒,却能让你的清理脚本变得安静又可靠。

坑三:备份和数据库放在同一块磁盘上。delete input的安全性假设是“备份和原始文件不同盘”。如果备份集也放在数据库主机的那块数据盘上,磁盘一坏,备份和归档同时都没了,所谓备份就只是形式上存在。生产环境里备份存储必须和数据库存储隔离,至少是独立磁盘,最好是异地或者云对象存储,具体视团队资源而定。这个原则比任何具体的删除命令都重要,因为它决定了删除归档后是否还有可恢复的退路。

4. 备份的最终目的:恢复实操与故障排查

4.1 restore 和 recover 先分清楚

恢复不是一个动作,而是两个阶段。restore是把备份集里的物理文件还原回数据库目录,例如把损坏的数据文件从备份中拷贝回来;recover则是把归档日志和当前 redo log 应用到这些还原后的数据文件上,使它们推进到一致或指定的时间点。你可以粗浅地理解为:restore 是“把上次备份还原”,recover 是“把备份之后的账补上”。

这是我反复强调的一点,因为在排查恢复问题时显得尤为重要。如果 restore 做完直接 open,数据库经常报不一致错误,因为你看到的是某个旧时间点的状态,recover 还没执行。反过来,如果归档日志缺失,recover 会中途停下,此时究竟跳过还是转为不完全恢复,需要仔细判断。

4.2 数据文件损坏恢复的完整过程

假设一个多数的场景:数据库还在运行,但某个数据文件损坏,即使数据库可能提示异常。第一步把损坏文件离线:

SQL> alter database datafile 4 offline;

然后用 RMAN 恢复这个文件:

RMAN> restore datafile 4; RMAN> recover datafile 4;

最后重新上线:

SQL> alter database datafile 4 online;

这是在线文件级恢复,整个数据库不需要关闭,只影响文件 4 所在的表空间。恢复完成后,recover会应用文件离线期间产生的所有必要 redo,包括归档日志和在线日志,最终把该数据文件和其他文件对齐到同一个 SCN。

如果损坏范围较大,或者控制文件也丢了,那就需要整个数据库恢复:

RMAN> startup mount; RMAN> restore database; RMAN> recover database; RMAN> alter database open;

此处的recover database会自动应用备份后到当前时刻的归档日志,数据库能够恢复到尽量接近故障时刻的状态。

4.3 不完全恢复与 resetlogs 的配合

当业务误删了某张表,或者希望恢复到某个误操作之前的时间点,就需要不完全恢复。流程是先定位精确时间或 SCN,再执行:

RMAN> run { set until time "to_date('2024-06-20 09:30:00','yyyy-mm-dd hh24:mi:ss')"; restore database; recover database; alter database open resetlogs; }

set until time指定恢复目标时间点,RMAN 会找到该时间点之前最近的数据文件备份和一系列归档日志,把数据库恢复到那个时刻。这里的resetlogs是不完全恢复之后的固定动作,它会重置联机重做日志的 sequence,生成一条全新的日志历史分支。执行完resetlogs后,数据库就能以新的日志序列继续运行。

这里必须强调一个后续动作:不完全恢复后,立即做一次全备。因为resetlogs改变了日志序列,旧的备份和归档日志在恢复链路里可能与新的日志分支不再衔接,原有备份集对于新序列的数据库来说不能直接用于恢复到更早的时间点。所以“恢复完成”不是终点,备份重新生成才是。

4.4 恢复失败时怎么排查

恢复过程中最常见的是ORA-00283或ORA-01110,它们往往指向缺失或损坏的归档日志。排查思路分四步:

  1. 查看告警日志(alert log),其中记录了恢复尝试时遇到的错误和具体文件路径。
  2. 查询动态视图:
SQL> SELECT * FROM v$recovery_log;

这个视图会列出恢复过程中需要应用的归档日志序列范围,以及已应用和待应用的状态。配合v$log_history可以定位缺失文件。

  1. 判断是否可以做完全恢复。如果缺失的归档发生在恢复路径中间,后续归档就算存在也无法连续应用,完全恢复不可能了。此时要么找备份源把缺的归档restore archivelog回来,要么退而求其次,把set until time定位在缺失归档之前最近的一个可用时间点,做不完全恢复。

  2. 如果换了主机或控制文件被重建,RMAN 可能认不出备份集。这时用目录导入命令重新注册备份目录:

RMAN> catalog start with '/backup/rman';

执行完它会把目录下的备份片信息写回控制文件或目录库,RMAN 就能找到可用的备份集,再继续 restore。很多“备份明明在却恢复不了”的诡异情况,根本原因就是备份集没有被 RMAN 的元数据识别。

5. 监控归档增长与日常运维建议

5.1 用视图实时观察归档和备份状态

与其等归档把磁盘占满再抢救,不如把监控提前做起来。最常用的视图是v$recovery_area_usage:

SELECT * FROM v$recovery_area_usage;

它给出快速恢复区里各个文件类别(归档日志、备份集、闪回日志等)的使用率。如果归档日志的百分比持续超过 70%~80%,说明归档清理没跟上,需要检查最近一次备份是否成功、delete input是否真的生效。

日志切换频率也可以监控:

SELECT TO_CHAR(first_time, 'yyyy-mm-dd hh24') hour, COUNT(*) FROM v$log_history WHERE first_time > sysdate - 7 GROUP BY TO_CHAR(first_time, 'yyyy-mm-dd hh24') ORDER BY 1;

如果某些的每小时切换数量异常多,说明该时段 redo 生成量很大,可能是夜间批量任务或调度作业产生的。我见过一条低效的 update 语句每小时触发几十次日志切换,归档文件数量暴涨,直接把 FRA 撑爆。这类问题光靠删归档只是治标,还需要配合 SQL 调优、调整批量任务或者日志组大小,才能降低 redo 产生速率。

5.2 把备份、清理、监控做成闭环

最后聊聊日常运维的闭环设计,而不是零散的命令。我推荐在定期流程里组合下面几件事:

  • 每天按策略执行一次数据文件备份(全备或增量),命令中带上plus archivelog delete input,让归档清理自动发生。
  • 配置保留策略为RECOVERY WINDOW OF 7 DAYS或符合业务需求的窗口,执行delete obsolete清理过期的备份集。
  • 每周至少跑一次crosscheck archivelog all和crosscheck backup,再执行delete expired,把外力删除文件产生的元数据残留清掉。
  • 每个月或每两个季度做一次真实的恢复演练,把 RMAN 备份恢复到测试环境,验证备份可用的状态。

构建这个闭环的动机来自一个教训:很多环境备份跑得很勤,但从没验证过恢复路径,最后真出问题时才发现某个备份片损坏、归档空缺、控制文件信息不完整。恢复演练的意义,就是在真正出事前把这些缺陷暴露出来,把“理论上能恢复”变成“实测能恢复”。

我个人在实际操作中的体会是,RMAN 备份恢复和删除归档,真正考验人的不是某条命令怎么写,而是你是否能把备份和删除放在同一条安全的逻辑链路上:先有备份,再删原始归档;先有元数据一致,再动文件;先有恢复演练,再放心的策略。最后再分享一个小技巧:每次手工删除归档之后,顺手执行一下list backup summary;和list archivelog all;,确认 RMAN 记录的状态和磁盘上真实情况一致。几次之后,你会对自己这套数据库的归档生成速度、空间占用节奏形成非常直观的判断,应对突发状况时心里也就不慌了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进