接手这套 Linux 环境下的 Oracle 11g 数据库没多久,就被一次凌晨的误操作给教育了。运维同事在业务表上做批量更新时 where 条件漏了关键值,影响了近 20 万行数据,而当时手里只有每天凌晨 2 点通过 expdp 定时导出的逻辑备份。最后恢复倒是恢复了,但是整个链路走了差不多三个小时,中间还夹杂着表结构依赖、触发器重放、序列不同步这些乱七八糟的问题。那次之后我做的第一件事,就是把这套库的备份体系从 expdp 逻辑备份切换成 RMAN 物理备份,并按"全量+增量+crontab 定时任务"的节奏落地运行。这篇就是把整个实施过程、脚本设计和踩过的坑完整梳理一遍。
我这边环境是 CentOS 7.9,Oracle 11.2.0.4 单实例,没有上 RAC,数据库也不是特别大,数据体量大概在 700GB 左右,归档产生速度每天约 5GB 到 8GB。这个量级也是我想重点说的地方:RMAN 全量+增量+定时任务的方案,其实最适合的就是这种几百 GB 到几 TB 的中小型库。如果你手上有几十 TB 的仓库系统,那大概率要上增量备份 + 磁带 + 更多容灾手段的组合,本文这个思路可以当参考,但别直接套用。
1. 为什么最终选了 RMAN 全量+增量这套组合,而不是 expdp 或冷备份
先说清楚一个容易被忽略的点:备份方案的选择,本质上是"恢复时间"和"备份成本"之间的博弈。很多人一上来就问"用什么备份工具",其实应该先问"如果数据库坏了,你准备花多长时间恢复、能接受丢多少数据"。
1.1 expdp 逻辑备份的问题在哪里
expdp 导出的只是逻辑数据,不是数据库文件级别的物理副本。它本身并不是不能用,而是当你的恢复目标变成"把数据库恢复到 N 分钟前那个状态"时,逻辑备份会非常吃力。
举个例子,原来我们每天凌晨用 expdp 做全库导出,但是白天某张表被误删了数据,你想恢复到误删之前,怎么办?你只有昨天凌晨 2 点的 expdp 文件,恢复出来之后,昨天凌晨 2 点到今天上午之间所有的新增数据全部丢失。如果这个间隔里恰好有重要的业务单据、账务流水,那就只能补录或者回溯上游接口,这个工作量通常比恢复本身还要大。
另外 expdp 恢复是"数据导入"的过程,如果源库有非常复杂的对象依赖、自增序列、触发器、外键约束,导入时顺序搞错会导致大量的报错。再加上几百万行的表导入本来就慢,这个恢复速度在灾难场景下是致命的。
1.2 冷备份为什么没进入候选
冷备份(数据库正常 shutdown 之后把数据文件、控制文件、日志文件直接拷贝一份)在理论上是可靠的,一致性也最好,但是它对业务的影响是"整个数据库必须停机"。700GB 的库,正常 shutdown 其实几秒钟就完成了,但是 cp 700GB 文件,走千兆网络、普通 SAS 盘,差不多要一个小时以上。如果数据库是 7x24 小时有业务在跑,这个停机时间基本无法接受。
而且冷备恢复虽然简单,但它没有任何增量机制。今天冷备完,明天数据库崩了,只能恢复到昨天晚上冷备那个时间点,中间丢了一天。这个数据丢失量在很多业务场景下是不合格的。
1.3 RMAN 全量+增量真正解决的是什么
RMAN 是 Oracle 自带的物理备份工具,它备份的是真实的数据文件、控制文件、spfile,并且能在备份件里记录 SCN 信息。这意味着它支持真正的"增量"概念——以 SCN 为基准,只备份自上次备份以来发生过变化的块。
在恢复侧,RMAN 的能力就很直观了:用全量备份件把数据库恢复到全量那个点,然后用增量备份把数据推进到增量那个点,最后再通过应用归档日志把数据库推到故障前那一刻。这个恢复路径比 expdp 导入要快得多,因为备份件是 Oracle 内部直接识别的文件格式,不需要重新组装表结构、约束、触发器等逻辑对象。
所以最终我选择的是这样一套节奏:
- 每周日凌晨做一次 RMAN 全量备份(Level 0);
- 周一到周六凌晨做一次 RMAN 增量备份(Level 1);
- 每次备份时顺便备份归档日志,并在确认归档日志备份成功后删除本地的归档文件;
- 通过 crontab 定时任务每天自动执行,并加上日志和告警机制;
- 每个月底做一次恢复演练。
这套方案能保证:任何时间点发生故障,理想情况下最多丢失上一次归档日志备份之后重新产生的少量归档,恢复时间也就是"全量恢复+增量应用+归档应用"的总时长。对于量级在 TB 以下的生产库,这个 RTO/RPO 在可控范围内。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开工前的三件事:归档模式、备份目录、磁盘空间预算
很多第一次搭 RMAN 的人容易一头扎进脚本里,结果跑到一半发现数据库没开归档,或者备份目录所在的文件系统空间不足。准备工作其实比写脚本重要得多。
2.1 确认并切换归档模式
RMAN 如果不开归档,那么你只能做一致性冷备份,增量备份是跑不了的,因为联机重做日志切换后旧日志会被覆盖,没有历史归档可供增量恢复时使用。
可以通过下面的 SQL 确认当前数据库是否为归档模式:
sql复制SQL> SELECT log_mode FROM v$database;
LOG_MODE
------------
NOARCHIVELOG
如果返回 NOARCHIVELOG,就需要执行以下操作切换:
sql复制SQL> SHUTDOWN IMMEDIATE;
SQL> STARTUP MOUNT;
SQL> ALTER DATABASE ARCHIVELOG;
SQL> ALTER DATABASE OPEN;
SQL> ALTER SYSTEM ARCHIVE LOG START;
SQL> SELECT log_mode FROM v$database;
切换完成后建议设置一个显式的归档目录。常见的做法有两种:一是使用快速恢复区(FRA),二是单独指定 log_archive_dest。我这边没有把归档和数据库文件放在同一个文件系统上,而是单独挂了一块盘,路径为 /u01/archivelog。
sql复制SQL> ALTER SYSTEM SET log_archive_dest_1='LOCATION=/u01/archivelog' SCOPE=BOTH;
SQL> ALTER SYSTEM SET log_archive_format='arch_%t_%s_%r.arc' SCOPE=SPFILE;
因为 log_archive_format 是静态参数,所以我用 SCOPE=SPFILE 让它下次启动生效。
2.2 备份目录结构规划
备份目录如果不提前规划,跑一段时间后就会变成一锅粥。我这边设计的目录结构如下:
code复制/u01/rman_backup/
├── full/ -- 全量备份集
├── inc/ -- 增量备份集
├── arch/ -- 归档日志备份集
├── logs/ -- 备份日志输出
└── scripts/ -- 备份脚本
目录分工明确,后续做磁盘空间排查、备份文件清理时非常省心。另外注意所有目录的属主和权限要正确,RMAN 通过 oracle 用户执行,目录也必须让 oracle 用户有读写权限。
bash复制mkdir -p /u01/rman_backup/{full,inc,arch,logs,scripts}
chown -R oracle:oinstall /u01/rman_backup
chmod -R 750 /u01/rman_backup
2.3 磁盘空间预算与保留策略
空间预算是很多人容易忽略的一环。如果备份文件把文件系统写满了,轻则备份失败,重则影响数据库正常运行。
我按照自己的环境做了一个简单的估算:
- 数据库总体积约 700GB;
- 每周全量备份压缩后大约 320GB(考虑到很多块是空的,压缩率不错);
- 每天增量备份约 20GB 到 40GB;
- 每天归档日志约 5GB 到 8GB,备份后即可删除;
- 保留策略:全量备份保留 2 份,增量备份保留 7 天。
也就是说,/u01/rman_backup 所在文件系统至少要留出 320GB x 2 + 40GB x 7 + 8GB ≈ 968GB 的空间。我这边实际分配了 1.2TB 的独立挂载点,并且通过 RMAN 的保留策略(delete obsolete)自动清理过期备份集,防止备份文件无限膨胀。
如果你不想让备份文件长期占着大量磁盘,最简单的方式是在 RMAN 中设置保留策略,比如下面的配置:
sql复制RMAN> CONFIGURE RETENTION POLICY TO REDUNDANCY 2;
这意味着每个数据文件保留最近 2 份备份,超出后会被标记为 obsolete,配合 delete obsolete 就能自动清理。
3. 全量备份(Level 0)的脚本实现与有效验证
全量备份是整个备份链条的地基。虽然 RMAN 里真正意义上的全量备份可以用 backup database 直接做,但在后续要接增量的场景下,我更建议把它显式定义为 Level 0 增量基础备份。因为在 RMAN 的增量备份体系里,Level 0 就是增量的"基准",后续所有 Level 1 增量都是基于 Level 0 来累积的。
3.1 全量备份脚本
这是我实际在用的一个全量备份脚本,你可以直接参考:
bash复制#!/bin/bash
# =====================================================
# 脚本: full_backup.sh
# 功能: Oracle RMAN 全量备份 (Level 0)
# 定时: 每周日 01:30
# 作者: DBA 日常实践
# =====================================================
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.2.0/dbhome_1
export PATH=$ORACLE_HOME/bin:$PATH
export NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'
BACKUP_BASE=/u01/rman_backup
LOG_DIR=$BACKUP_BASE/logs
LOG_FILE=$LOG_DIR/full_backup_$(date +%Y%m%d_%H%M%S).log
LOCK_FILE=/tmp/rman_full_backup.lock
# 防止脚本重入
if [ -f "$LOCK_FILE" ]; then
echo "$(date '+%F %T') 另一个全量备份任务还在运行,本次退出。" >> $LOG_DIR/full_backup_skip.log
exit 1
fi
touch $LOCK_FILE
trap 'rm -f $LOCK_FILE' EXIT
rman target / log=$LOG_FILE <<EOF
run {
# 分配两个磁盘通道,提升并行度
allocate channel c1 type disk;
allocate channel c2 type disk;
# 归档当前日志,确保备份一致性
sql 'alter system archive log current';
# 全量备份数据库,包含当前控制文件
backup as compressed backupset database
format '$BACKUP_BASE/full/full_%d_%T_%s_%p.bkp'
tag 'FULL_LEVEL0'
include current controlfile;
# 备份 spfile
backup spfile
format '$BACKUP_BASE/full/spfile_%d_%T_%s_%p.bkp'
tag 'FULL_SPFILE';
# 备份归档日志,备份成功后删除已备份的归档
sql 'alter system archive log current';
backup as compressed backupset archivelog all
format '$BACKUP_BASE/arch/arch_%d_%T_%s_%p.bkp'
tag 'FULL_ARCH'
delete input;
# 检查备份记录与物理文件的一致性
crosscheck backup;
crosscheck archivelog all;
# 清理超过保留策略的过期备份
delete noprompt obsolete;
release channel c1;
release channel c2;
}
exit;
EOF
3.2 脚本里几个容易被忽略的设计
在脚本里,backup as compressed backupset database 是核心指令。这里我用了压缩备份,因为数据库里有大量空闲块和重复块,压缩后备份体积能减少一半甚至更多,当然压缩会消耗 CPU,不过在凌晨低峰期跑,完全能接受。
include current controlfile 很关键。控制文件里记录了整个数据库的结构信息和备份元数据,如果没有这一步,恢复时可能要先重建控制文件,会增加不少麻烦。
crosscheck backup 的作用是校验 RMAN 元数据记录与实际磁盘文件是否一致。如果某个备份文件被外部删除,crosscheck 会把状态标记为 EXPIRED,方便后续处理,同时也能防止恢复时引用一个根本不存在的备份件报错。
delete noprompt obsolete 的作用是清理过期的备份集。注意它和 delete expired 不是一回事:obsolete 是"我自己设定的保留策略之外,不再需要的备份",expired 是"物理文件已经不存在,但 RMAN 里还有记录的备份"。我两个都会用,先用 crosscheck 检查,再用 delete obsolete 清理过期项,这样磁盘空间能持续回收。
3.3 全量备份之后怎么验证
备份跑完不等于备份可用。我见过太多人只看日志末尾有"RMAN-03090"消失、没有 ERROR 就认为备份成功了。实际上更严谨的做法是用 RMAN 的 validate 命令做一次备份可读性验证,尤其是第一次搭这套环境的时候。
bash复制rman target / log=/tmp/validate.log <<EOF
restore database validate;
exit;
EOF
restore database validate 会从备份集中读取所有数据文件,验证它们是否可以正常还原,但不会实际写出数据文件。这个操作不会影响当前数据库,却能在不真正恢复数据库的情况下,提前发现备份件的损坏情况。
我第一次跑 validate 时还真抓到过一个备份文件异常,原因是备份执行过程中磁盘满,生成了一个不完整的备份集。当时备份日志里其实也有报错,但因为是凌晨跑的,没有第一时间看,幸好 validate 阶段发现了问题。自那之后,我养成了习惯:新环境备份方案上线前,全量备份后必须跑一次 validate,确认无误再接增量。
4. 增量备份(Level 1):差异与累积怎么选,脚本怎么写
增量备份是整个方案里最需要动脑子设计的地方,因为它既影响备份体积,又影响恢复时的时长。
4.1 差异增量与累积增量的取舍
RMAN 的 Level 1 增量备份有两种模式:
- 差异增量(differential):默认模式,备份自上次任意级别(Level 0 或 Level 1)备份以来发生变化的所有块。
- 累积增量(cumulative):备份自上次 Level 0 备份以来发生变化的所有块。
两者的区别可以用一个场景很好理解。假设周日做了 Level 0,周一做了差异 Level 1,周二做累积 Level 1,那么周二的累积增量包含周一到周二这两天的全部变更块;而如果周二做的是差异增量,只包含周一到周二之间新增的变化块(因为上一次 Level 1 是周一)。累积增量每次都要从周日的 Level 0 开始扫描,所以单次备份体积和耗时更大,但它恢复更快——只需要"全量 + 这周累积"两份件;而差异增量恢复时需要"全量 + 周一的增量 + 周二的增量 + ..."逐层应用。
下表可以直观对比:
| 比较维度 | 差异增量 | 累积增量 |
|---|---|---|
| 单次备份大小 | 较小 | 较大 |
| 备份耗时 | 较短 | 较长 |
| 恢复时需要几个增量 | 所有中间增量 | 只用一个 |
| 适用场景 | 每日变更量大、恢复频率低 | 恢复速度优先、每日变更量少 |
我这边选择的是差异增量。理由是数据库每日变更量不大(每天几十 GB),备份时间可控,恢复时多应用几次增量也完全在可接受范围内。
4.2 增量备份脚本设计
增量脚本和全量脚本结构类似,区别是备份对象从 database 变成 incremental level 1 database:
bash复制#!/bin/bash
# =====================================================
# 脚本: inc_backup.sh
# 功能: Oracle RMAN 差异增量备份 (Level 1)
# 定时: 每周一至周六 01:30
# =====================================================
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.2.0/dbhome_1
export PATH=$ORACLE_HOME/bin:$PATH
export NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'
BACKUP_BASE=/u01/rman_backup
LOG_DIR=$BACKUP_BASE/logs
LOG_FILE=$LOG_DIR/inc_backup_$(date +%Y%m%d_%H%M%S).log
LOCK_FILE=/tmp/rman_inc_backup.lock
if [ -f "$LOCK_FILE" ]; then
echo "$(date '+%F %T') 另一个增量备份任务还在运行,本次退出。" >> $LOG_DIR/inc_backup_skip.log
exit 1
fi
touch $LOCK_FILE
trap 'rm -f $LOCK_FILE' EXIT
rman target / log=$LOG_FILE <<EOF
run {
allocate channel c1 type disk;
allocate channel c2 type disk;
sql 'alter system archive log current';
# 差异增量备份 Level 1
backup as compressed backupset incremental level 1 database
format '$BACKUP_BASE/inc/inc_%d_%T_%s_%p.bkp'
tag 'INC_LEVEL1';
# 备份控制文件
backup current controlfile
format '$BACKUP_BASE/inc/ctl_%d_%T_%s_%p.bkp'
tag 'INC_CONTROLFILE';
# 备份归档日志并删除已备份的本地归档
backup as compressed backupset archivelog all
format '$BACKUP_BASE/arch/arch_%d_%T_%s_%p.bkp'
tag 'INC_ARCH'
delete input;
crosscheck backup;
crosscheck archivelog all;
delete noprompt obsolete;
release channel c1;
release channel c2;
}
exit;
EOF
增量备份的归档日志备份同样关键。因为增量备份是基于差异的,如果在两个增量之间你丢失了这段时间的归档日志,即使有增量备份也无法做完整的介质恢复。所以我在每个增量阶段都会先 alter system archive log current 强制切换日志,把当前 redo 落成归档,再一起备份并删除本地归档。
4.3 增量链的衔接:什么时候要重新做 Level 0
这是我实际运维中发现的一个容易被忽略的点。随着 Level 1 增量备份次数变多,如果长期不做 Level 0,会出现两个问题:
-
最后一次 Level 1 增量与 Level 0 之间的"距离"越来越远。RMAN 在做 Level 1 时,需要读取从 Level 0 以来所有变化过的块的位图信息,数据量会越积越多,备份时间变长,备份体积变大。
-
恢复时,如果按顺序应用 Level 1 增量,需要逐个读取每一次增量备份,增量链越长,恢复时间越长。
所以我的策略是:每周固定做一次 Level 0 全量备份,把增量链重置。如果遇到特殊情况,比如某几天归档日志量激增、数据库结构变化很大,我也会考虑在周中额外补一次 Level 0。这个判断没有绝对的标准,就看增量备份的耗时是否明显上升、恢复时应用增量链表的时间是否可接受。
5. crontab 定时调度:从"能跑"到"不会跑重叠、不会静默失败"
脚本手跑没问题之后,才轮到真正的定时任务落地。这一步看起来只是加一行 crontab,但实际要考虑任务重叠、日志清理、异常告警等问题。
5.1 crontab 配置
先看最核心的配置:
bash复制# Oracle RMAN 定时备份任务
30 1 * * 0 /u01/rman_backup/scripts/full_backup.sh >> /u01/rman_backup/logs/cron.log 2>&1
30 1 * * 1-6 /u01/rman_backup/scripts/inc_backup.sh >> /u01/rman_backup/logs/cron.log 2>&1
这里我把全量放在每周日凌晨 1:30,增量放在每周一至周六凌晨 1:30。选凌晨 1:30 是因为这个时段业务量最低,日志切归档量小,备份对业务的影响最小。
为什么用 1:30 而不是 1:00?其实没有特殊原因,主要是给 1:00 的系统日志轮转、其他脚本留一点时间差,避免大家都在整点抢 I/O。
5.2 防止任务重叠
crontab 本身不会防止任务重叠。如果数据库压力大、备份速度变慢,前一个备份任务还没结束,后一个备份任务又被调度起来,两个 RMAN 进程同时写备份文件会互相干扰,轻则备份失败,重则产生一堆无用的半成品备份文件。
所以我在备份脚本开头加了一个锁文件判断,前面的脚本里已经写了。核心逻辑是:
- 脚本启动时如果发现锁文件存在,说明上一个备份还在运行,直接退出;
- 否则创建锁文件;
- 脚本执行完后,通过 trap 清除锁文件。
bash复制LOCK_FILE=/tmp/rman_full_backup.lock
if [ -f "$LOCK_FILE" ]; then
echo "$(date '+%F %T') 另一个备份任务还在运行,本次退出。" >> $LOG_DIR/skip.log
exit 1
fi
touch $LOCK_FILE
trap 'rm -f $LOCK_FILE' EXIT
注意锁文件要做成脚本级别的,不同脚本用不同的锁文件名,比如全量和增量分别用 rman_full_backup.lock 和 rman_inc_backup.lock。这样全量正在跑的时候,理论上增量不应该同时跑;但如果是特殊调试场景,你也可以根据实际情况决定是否允许不同脚本并行。
5.3 日志与告警
备份日志非常重要。crontab 执行的任务有个特点:如果脚本没有把输出重定向到文件,crontab 默认会发邮件给本地用户,但这个邮件往往没人看。所以我将脚本输出全部重定向到了 cron.log,同时每个脚本内部也用 RMAN 的 log 参数把 RMAN 自身输出单独写到带时间戳的日志文件里。
我还会在脚本末尾增加一段判断,用 grep 检查 RMAN 日志里是否有 ERROR 等关键错误,如果有就退出码置为非 0,并在日志中标记失败。这样运维监控系统可以采集到异常事件。
bash复制if grep -qiE "RMAN-|ORA-|ERROR" $LOG_FILE; then
echo "$(date '+%F %T') RMAN 备份日志中发现错误,请检查!" >> $LOG_DIR/cron.log
exit 1
fi
告警这一块,我没有引入重量级的监控系统,而是直接在脚本里做了一个简单的通知:RMAN 日志中出现错误时就往团队邮件组发一封告警邮件。虽然 esmtp 配置起来稍微麻烦一点,但能第一时间发现问题,比备份失败后无人知晓要强太多。
6. 恢复演练:把备份链路完整走一遍
无论备份脚本写得再完善,如果恢复流程没有验证过,这个备份体系的价值就要打一个大大的问号。我见过不少环境,备份天天成功,一旦真发生故障要恢复时才发现备份根本没用。
6.1 一次完整的还原恢复流程
RMAN 完整的恢复主要分三步:restore 数据文件、recover 数据文件、打开数据库。
在测试环境模拟一次完全恢复,流程如下:
bash复制rman target / log=/tmp/restore_test.log <<EOF
startup mount;
restore database;
recover database;
alter database open;
exit;
EOF
restore database 会从备份集中把数据文件还原到原位置。recover database 则负责应用归档日志和增量备份,把数据库推进到最新状态。如果数据文件确实损坏且最近的备份不可用,RMAN 会在这里报错。
注意执行前要确保测试环境的磁盘空间足够大,restore database 会把数据文件真实写出来,700GB 的库恢复后同样需要 700GB 的空间。
6.2 恢复到某个时间点
比完全恢复更常见的场景是"误删除数据,想恢复到误删除之前的时间点"。RMAN 用 until time 指定恢复目标时间:
bash复制rman target / log=/tmp/restore_pit.log <<EOF
startup mount;
restore database until time "to_date('2026-03-30 14:30:00','YYYY-MM-DD HH24:MI:SS')";
recover database until time "to_date('2026-03-30 14:30:00','YYYY-MM-DD HH24:MI:SS')";
alter database open resetlogs;
exit;
EOF
这种时间点恢复(PITR)依赖归档日志。如果你的备份机制里不包含归档日志的备份,那 until time 只能恢复到最近一次归档所在的时间点,无法再往前推。这也是我在全量和增量脚本里都备份归档日志的原因。
alter database open resetlogs 在恢复后必须执行,因为它会重置 redo log 的历史,建立一条新的日志时间线。执行完成后建议立刻做一次数据库全量备份,让新的日志链在恢复体系里立得住。
6.3 演练节奏和关键检查项
我个人的节奏是每月最后一周在测试环境做一次完整恢复演练,重点检查:
- 备份集完整性:list backup summary 是否有 EXPIRED 状态的备份;
- 归档日志是否连续:恢复是否能一路推到最新状态;
- 恢复耗时:从 restore 到 open 一共花了多久,这个指标直接决定了真实故障时的 RTO;
- 应用可用性:数据库起来之后,选几个关键业务表做数据校验。
演练中如果发现恢复时间超过了团队的容忍上限,就要回头优化备份策略,比如把差异增量改成累积增量、增加全量频率、调整通道数量等。这些优化必须在演练阶段完成,不能等真出故障才去调。
7. 实际跑下来遇到的坑,逐个复盘
搭这套方案的过程中,我踩过不少坑,挑几个有代表性的说一下,希望对你有帮助。
7.1 快速恢复区空间打满导致 ORA-19809
一开始我把归档日志目的地设置在了快速恢复区(FRA),没有太在意它的容量上限。跑了大概三周之后,某天增量备份开始报 ORA-19809: limit exceeded for recovery files,数据库的归档彻底写不进去了。
排查过程是先看告警日志,发现 FRA 使用率达到了 99%。原因是归档日志备份成功后虽然删除了本地归档文件,但 FRA 的使用率并不会立即下降,因为 FRA 里还有其他文件(比如早期的控制文件自动备份),而且删除操作本身只在备份完成后才发生。最终我做了两件事:一是把归档日志目的地移到独立的 /u01/archivelog 目录,不再依赖 FRA;二是定期用 delete archivelog all 配合备份策略自动清理归档。这个问题在切换目录后就没再复发过。
7.2 RMAN 备份文件把文件系统写满
还有一次是备份目录所在文件系统被写满了。原因是没有严格执行 delete obsolete,因为保留策略只是把超出的备份标记为 obsolete,如果没有执行 delete,那些"过期但未删除"的备份文件会一直占着磁盘。
这也是为什么我在备份脚本里把 delete noprompt obsolete 放在每次备份之后的原因。你要记住,RMAN 的保留策略只负责"标记",不负责"删除",物理删除必须显式触发。
7.3 增量备份越跑越慢
增量备份在连续跑了几周之后,耗时明显增加。后来分析发现是增量备份的数据量与"上次 Level 0 以来变化块数量"直接相关,而我在中途没有重置 Level 0 的基准。也就是说增量链越来越长,增量备份每次要处理的块也越来越多。
解决方式就是我前面说的——每周固定做一次 Level 0 全量备份,把增量链重置。如果某天发现增量备份耗时翻倍,先查这两点:一是数据库是否长时间没做过 Level 0;二是归档日志是否在快速增长(通常是业务量变大或某个大事务造成)。找准原因再做调整。
7.4 误删控制文件后恢复时找不着备份
有一次我想在测试库上模拟控制文件损坏的场景,发现 restore 控制文件时报 RMAN-06023,说找不到备份或 copy。原因是我的备份脚本虽然用了 include current controlfile,但控制文件的自动备份功能(control_file_autobackup)没有打开,导致在控制文件丢失后,RMAN 无法自动定位到最近的备份位置。
最终我把 CONTROLFILE AUTOBACKUP 打开,并设置了自动备份格式。现在每次全量和增量备份,RMAN 都会自动备份控制文件,这样即使控制文件完全丢失,也能通过自动备份恢复。
7.5 环境变量和 NLS_DATE_FORMAT 的坑
最后一个坑不是 RMAN 本身,而是脚本环境。crontab 执行时和手动登录 shell 执行时,环境变量是不同的。我的脚本一开头已经显式 export 了 ORACLE_SID、ORACLE_HOME、PATH,否则 crontab 里执行会报 "ORACLE not properly configured" 之类的错误。
另外,RMAN 里很多输出会显示时间,这里的日期格式受 NLS_DATE_FORMAT 控制。如果不设置,默认格式可能不包含时分秒,恢复时看日志会分不清哪一步到底是什么时间执行的。所以我在脚本里固定导出了 NLS_DATE_FORMAT,这个习惯能帮你少费很多查日志的时间。
这套全量+增量+定时任务的备份体系上线已经跑了一个季度。最直观的感受是:以前 expdp 时代,每次看到"备份成功"的日志,心里其实并不踏实;现在切换到 RMAN 之后,每一次恢复演练都能把数据库完整拉起来,心里那根弦反而松了很多。如果你也想给自己管理的数据库上这套方案,我的建议是别急着写脚本,先把归档模式、备份目录、空间预算和保留策略想清楚,再把脚本一条条加进去,最后务必做一次真实的恢复演练。备份这件事,平时看起来不产生业务价值,但关键时刻就是数据库的最后一道防线。最后再多说一句,脚本里的备份目录、保留策略、恢复流程这些,最好在文档里留一份记录,省得半年之后自己都忘了当初为什么这么配。
