1. 理解ADG Gap:这个故障的本质
1.1 先说清楚ADG的同步机制
Oracle Active Data Guard(ADG)作为生产环境最常见的高可用方案,核心机制其实不复杂:主库产生redo日志,通过归档或实时传输送到备库,备库的MRP进程(Managed Recovery Process)把收到的日志应用到自己的数据文件上。听起来流水线一样顺畅,但只要是流水线,就有卡壳的时候。
主库和备库之间的“日志传输-应用”链路一旦中断,备库接收到的日志序号和主库已生成的日志序号之间就会出现一段空档,这个空档就是Gap。可以用一个更直观的方式理解:主库已经走到100号日志了,备库还停在92号,中间93到99这些日志备库要么没收到,要么收到了还没来得及应用,这时候备库和主库之间的差距已经不是简单的时间滞后,而是一段真实的日志空洞。
Gap最麻烦的点在于它不是“等一下就能自动恢复”的问题。主库的归档日志是一直往前走的,备库处于Gap状态时,MRP会尝试去抓缺失的日志,但如果抓不到,备库就会一直卡在那里。最严重的情况下,备库直接失去容灾意义——主库一旦在Gap期间宕机,数据丢失量不再是几秒级别,而可能是一整天甚至更久。
1.2 Gap到底是怎么产生的
结合我处理过的故障场景,Gap的产生原因大致可以归为以下几类:
网络层面的问题是最常见的。主备库之间的网络如果出现长时间抖动或断连,redo传输会中断。特别要注意的是,很多生产环境的网络策略是“丢包重传”,但重传机制有时候不适用于大批量日志传输,比如归档日志文件过大、传输队列堆积,都会导致Gap不断拉大。
备库自身的问题也不少。最典型的是备库磁盘空间满了。备库接收归档日志需要空间,应用redo日志也要空间,如果flash recovery area或者数据文件所在文件系统被塞满,MRP进程会异常终止,Gap就会产生。还有一种是备库的standby redo log组数不够或者size设置不合理,导致日志无法正常归档到备库。
主库侧的日志丢失也会引发Gap。比如某公司在归档目录被误清理、磁盘故障导致部分归档文件损坏,主库这边已经不存在的日志,备库自然无法接收。FAL(Fetch Archived Log)机制会尝试从主库拉取缺失日志,但如果主库自身也没有那一段日志,Gap就变成了“死结”。
还有一个容易被忽视的原因:备库曾经被重启或者做过某种恢复操作,MRP进程没有正常拉起,但redo传输一直没有停。等你想起来去查备库状态的时候,Gap可能已经积累了很长一段。
1.3 为什么Gap特别危险
很多刚接触ADG的同事会问:备库落后一点有什么关系,反正是容灾,平时又不读备库。
这个想法在Oracle 11g之后尤其危险。ADG(Active Data Guard)的一个核心特性就是备库可以以只读方式打开,承担一部分查询和报表压力。备库一旦出现Gap,就意味着备库上的数据是不一致的——你从备库查到的可能是昨天的数据,而业务方默认的却是“实时备库”。
更重要的是,Gap期间主库如果发生任何需要failover的事件,你会面临一个痛苦的抉择:激活备库,但丢失大量数据;或者不激活备库,业务完全停摆。很多公司在做容灾演练时最不愿看到的就是这个场景。
所以处理Gap的核心原则就一句话:发现越早,恢复越快。Gap初期修复可能只需要几分钟,拖到后面就可能变成一次完整的备库重建。
2. 快速定位:如何确认备库落后了多少
2.1 “三步走”定位法,五分钟后就能知道具体缺了哪些日志
确认Gap有三个固定SQL,我每次处理此类问题时都是这套组合拳。
第一步,手工发起日志归档,确保主库所有redo已经转为归档:
-- 在主库执行 ALTER SYSTEM ARCHIVE LOG CURRENT;这一步很关键,很多Gap产生的直接原因是主库最新redo还没有归档,备库根本无法获取到最新日志。手动触发归档后,主库的日志序号会连续起来,备库最多差的就是已经归档过的那部分。
第二步,查询Gap具体范围:
-- 在备库执行 SELECT * FROM V$ARCHIVE_GAP;这个视图是专门为Gap设计的,查询结果会直接告诉你缺少哪一段日志。比如返回一行数据,THREAD#为1,LOW_SEQUENCE#为93,HIGH_SEQUENCE#为99,就说明93到99这7个日志你需要找到并补上。
第三步,对比两边的日志序列号:
-- 主库执行 SELECT THREAD#, MAX(SEQUENCE#) FROM V$ARCHIVED_LOG GROUP BY THREAD#; -- 备库执行 SELECT THREAD#, MAX(SEQUENCE#) FROM V$ARCHIVED_LOG GROUP BY THREAD#;两边一对比,你就能看出备库到底落后了多少。这里有个经验:如果主备库的MAX(SEQUENCE#)差值在1到2个以内,通常是正常延迟,因为最后生成的redo还没完成归档;如果差值大于2,就要关注是否有Gap了。
2.2 常见误判:明明查不到Gap却还是不同步
V$ARCHIVE_GAP查不到记录,并不代表你安全了。这个视图只反映MRP当前尝试获取但获取不到的日志,有一种情况它查不到,但备库确实是Gap状态——MRP没有被启动。
备库MRP进程如果挂了,备库不会应用日志,但日志接收可能还在进行。这时候你用V$ARCHIVE_GAP查询,大概率是空的,因为MRP根本没在尝试抓取任何日志,自然不会告诉你“我缺了什么”。真正的做法是查MRP进程状态:
-- 备库执行 SELECT PROCESS, STATUS, CLIENT_PID, THREAD#, SEQUENCE# FROM V$MANAGED_STANDBY WHERE PROCESS LIKE 'MRP%';如果查询结果返回空,或者STATUS不是APPLYING_LOG,说明MRP已经不正常了。最常见的场景是MRP进程因为空间不足或者日志损坏而终止,你需要在告警日志里找到MRP退出时的报错信息。
还有一种情况:V$ARCHIVE_GAP能查到数据,但显示的日志段实际上在备库本地已经存在。这是典型的“日志已经落盘但没注册”的情况。备库收到了归档文件,但控制文件里没有对应记录,MRP无法识别这些文件。这时候需要手动在备库注册这些归档文件:
-- 备库执行 ALTER DATABASE REGISTER LOGFILE '/path/to/archivelog/1_93_1234567890.arc';2.3 排查现场信息要记牢
处理Gap最怕的就是查了一半发现信息不全,来回切换数据库看状态。我建议在着手修复之前,先把下面这些信息一次性收集完整:
主备库的DB_UNIQUE_NAME、数据库版本、平台类型,因为不同版本之间FAL机制和RMAN行为的差异还挺大;备库的V$ARCHIVE_GAP结果、V$MANAGED_STANDBY里MRP和ARCH/RFS进程的状态;主库和备库各自的归档日志目录路径,可能要直接用操作系统命令查看文件;备库的flash recovery area空间使用率、磁盘剩余空间。
把这些记到一个临时文档里,再开始修复。处理故障时最忌讳一边翻文档一边查状态,容易越搞越乱。
3. 修复核心流程:从RMAN增量备份到拉起MRP
3.1 方案选型:为什么增量备份恢复是首选
Gap一旦出现,修复方案其实有好几种,但效率差异巨大。
第一种是手动拷贝缺失的归档日志到备库并注册。这种方式在Gap较小(比如就缺两三个日志)的时候完全够用,操作成本低,恢复精准。但如果Gap很大,比如积累了上百个日志,手动拷贝的效率就很低了,而且如果主库那边的日志已经被清理,这条路根本走不通。
第二种是直接从主库做全量备份,把备库完全重建。这个方式最暴力,但耗时最长。一个TB级别的数据库,全量备份加水传输加恢复,十几个小时都是正常的。中间如果网络再抖一下,又得重来。
第三种就是我要重点讲的:通过RMAN增量备份来恢复备库。原理很简单:用RMAN在主库做一个从某个SCN开始的增量备份,这个SCN正好对应备库当前的同步点,然后把增量备份传到备库应用。RMAN会把这些增量变化应用到备库的数据文件上,把备库推进到接近主库当前的位置。最后再启动MRP,把剩余的少量归档追平。
增量备份方案的优点在于只传输变化的数据块,耗时通常只有全量备份的十分之一甚至更少。对于数据量在TB级别的系统,这是唯一能在可接受时间内完成Gap修复的方案。
3.2 实操开始:RMAN增量备份制作与传输
先确定备库的SCN,这个值决定了增量备份从哪里开始:
-- 备库查询当前SCN SELECT CURRENT_SCN FROM V$DATABASE;严格来说,我们应该取备库数据文件头中的SCN,也就是V$DATAFILE_HEADER里的CHECKPOINT_CHANGE#,这样更精确。实际操作中,取V$DATABASE的CURRENT_SCN偏保守,但更安全,生成的增量备份包含的数据略多,不会有遗漏。
确定好SCN后,在主库执行RMAN增量备份:
# 主库执行 rman target /BACKUP INCREMENTAL FROM SCN <备库SCN> DATABASE FOR STANDBY FORMAT '/tmp/standby_incr_%U.bk';注意几个细节:
FOR STANDBY关键字不能漏,这个选项生成的备份文件不是普通备份,而是专门用于备库恢复的格式。INCREMENTAL FROM SCN后面填的是备库SCN,不是主库SCN,比较容易填错。- 备份文件会放到主库的/tmp或指定目录下,记得看一下空间够不够。增量备份的大小取决于从备库SCN到现在主库产生的数据变化量,变化大可能几十GB,变化小可能几百MB。
备份完成后,把生成的备份文件传到备库:
scp /tmp/standby_incr_*.bk oracle@standby_host:/tmp/这里有一个优化经验:如果备库和主库之间有专线,建议先压缩再传。增量备份文件虽然已经是数据库格式,但传输时直接压一下能节省大量时间:
gzip /tmp/standby_incr_*.bk scp /tmp/standby_incr_*.bk.gz oracle@standby_host:/tmp/传完之后在备库解压,然后进入RMAN恢复流程。
3.3 备库应用增量与后续操作
在备库上执行的恢复操作有一个大前提:MRP进程必须先停止。如果MRP还在跑,RMAN会和它冲突,导致恢复失败:
-- 备库执行,停止MRP ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL;然后进入RMAN,把增量备份注册并应用到备库:
# 备库执行 rman target /CATALOG START WITH '/tmp/standby_incr_'; RECOVER DATABASE NOREDO;这里的NOREDO是关键选项,意思是恢复时只应用增量备份中的变化,不尝试去应用redo日志。因为我们现在是在修复Gap,redo日志的缺口还没有补齐,如果带着redo恢复会报错。
恢复完成后,把备库重新置入实时恢复模式,MRP启动后会尝试自动补齐剩余的归档日志:
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION;到这里,MRP应该会开始正常工作。观察一段时间,再次确认Gap是否已经消除。
3.4 特殊情况:归档日志还在但Gap依旧
增量备份修复完成后,绝大多数场景下备库都能追平。但如果主库侧的归档日志还在,而MRP启动后V$ARCHIVE_GAP依然有记录,就要考虑另一种修复方式:手动补齐缺失的归档日志。
先通过V$ARCHIVE_GAP确认缺失的日志范围,去主库找到对应的归档文件,拷贝到备库的归档目录下。然后手动注册:
-- 备库执行 ALTER DATABASE REGISTER OR REPLACE LOGFILE '/archivelog_dir/1_93_1234567890.arc';这里有个细节:注册时如果归档文件已经被MRP尝试过但失败,通常会残留一些错误状态,用REGISTER OR REPLACE可以覆盖旧的注册信息。注册完成后,MRP会自动感知到新的归档日志并尝试应用。
这种手动补齐方式适合Gap较小的场景,操作简单直接,但前提是主库这些归档日志都还在。所以我在处理Gap时,第一步会先确认主库的归档保留策略,如果日志已经不在了,直接放弃手动方案,走增量备份路线。
3.5 修复完成后要做的事:验证,不要急着收工
追平之后不能直接收工,还需要验证备库的“健康度”。我的习惯是等待几分钟,让MRP稳定运行一会儿,然后执行下面几个检查:
-- 备库执行 SELECT PROCESS, STATUS, THREAD#, SEQUENCE# FROM V$MANAGED_STANDBY; -- 确认无Gap SELECT * FROM V$ARCHIVE_GAP; -- 对比日志缺口 SELECT THREAD#, MAX(SEQUENCE#) FROM V$ARCHIVED_LOG GROUP BY THREAD#;如果V$MANAGED_STANDBY中MRP状态为APPLYING_LOG,V$ARCHIVE_GAP为空,主备库的MAX(SEQUENCE#)差值在正常范围内,说明Gap修复完成。
还要检查备库的归档应用延迟:
SELECT NAME, VALUE, UNIT FROM V$DATAGUARD_STATS WHERE NAME IN ('apply lag', 'transport lag');正常情况下,apply lag应该显示为0或非常小的值。如果apply lag还在持续增长,说明MRP虽然启动了,但应用速度跟不上日志产生速度,这时候就要看备库的IO性能和redo日志大小是否匹配了。
4. 预防与日常巡检:让Gap不再反复出现
4.1 几个关键参数调优
Gap处理完了,重点在于预防。这么多年看下来,大部分Gap事故其实是可以通过参数配置和监控巡检提前规避的。
先看DG的传输与恢复参数。主库的LOG_ARCHIVE_DEST_n里有一个关键属性SYNC和ASYNC的选择。SYNC模式在主库提交前会等待备库确认收到日志,数据零丢失,但对主库性能影响明显;ASYNC模式主库不等待备库确认,性能好但极端情况下有丢日志的风险。很多生产系统选择ASYNC,那Gap出现的概率天然比SYNC高一些。
备库的FAL_CLIENT和FAL_SERVER参数要配置正确。FAL机制是备库主动去主库抓取缺失日志的关键手段:
FAL_SERVER = primary_db_unique_name FAL_CLIENT = standby_db_unique_name很多Gap就是因为FAL配置缺失,导致备库缺少日志后无法自动找主库补,只能等DBA手动干预。检查这两个参数的方式很简单:
SHOW PARAMETER FAL;另外一个是DB_FILE_NAME_CONVERT和LOG_FILE_NAME_CONVERT,用于主备库文件路径不一致时的自动转换。如果这个参数没配好,备库压根无法找到对应的数据文件,恢复进度会卡住,表现也是Gap持续增大。
做完这些参数检查之后,再确认一个关键配置:主库归档日志的保留时间。如果DG环境里主库的归档日志被过早删除,备库来不及拉取就会产生不可逆的Gap。建议归档日志至少保留够恢复备库一个完整增量周期的时间,这个周期根据你的备份频率来定,通常不要低于24小时。
4.2 日常巡检SQL清单
与其每次都等Gap报警,不如把巡检做成一个固定动作。我整理了一个每日巡检的SQL清单,可以存成脚本定时跑:
-- 1. 查看备库Gap视图 SELECT * FROM V$ARCHIVE_GAP; -- 2. 查看MRP状态 SELECT PROCESS, STATUS, THREAD#, SEQUENCE# FROM V$MANAGED_STANDBY WHERE PROCESS LIKE 'MRP%'; -- 3. 查看归档应用延迟 SELECT NAME, VALUE, UNIT FROM V$DATAGUARD_STATS; -- 4. 查看日志GAP SELECT NAME, VALUE FROM V$DATAGUARD_STATS WHERE NAME = 'gap_status';这四个SQL覆盖了Gap发现的最核心维度。我建议每天至少跑一次,如果业务高峰期频率可以更高。遇到告警时,第一件事就是跑这套SQL,五分钟内就能确认整体健康度。
再分享一个使用V$ARCHIVE_GAP的小技巧:这个视图的结果只在Gap存在且MRP活跃时才有数据。如果你查询结果为空,但备库日志应用延迟很大,一定要先查一下MRP进程是否还活着,不要被空结果误导。
4.3 从一次凌晨告警说起:网络断连的教训
有一次处理过一起典型的Gap事故:某公司环境主备库网络在夜间出现持续十分钟的抖动,主库仍在正常接收业务并产生redo日志,而备库的网络传输中断,队列里的redo传不过去。等网络恢复时,主库已经生成了20多个新日志。这种情况下,备库的RFS进程会自动抓取缺失的日志,正常情况下FAL机制会解决这个问题。
但问题出在一个细节上:主库的归档日志在传输过程中有一个日志文件因为网络抖动被写坏了,备库收到的归档文件校验失败。MRP尝试应用这个文件时直接报错退出,后续的日志也无法继续应用,Gap就此产生。
处理过程其实并不复杂,就是用我上面讲的方法定位、补齐、恢复。但事后复盘时最值得反思的是:如果备库的MRP异常退出时有成熟告警,Gap会被更早发现。很多团队的监控只看备库是否“存活”,但MRP进程活不活、日志应用有没有卡住,却没有监控覆盖。建议把MRP进程状态和apply lag作为两个独立的监控项,任何一个异常都立即告警。
5. 我踩过的坑:几个容易被忽略的细节
坑一:忘了停MRP就开始恢复。直接在备库跑RMAN恢复时,MRP还在运行,结果RMAN报错ORA-01154。处理任何DG恢复操作前,第一件事就是确认MRP已经停止。
坑二:SCN取值错误。做增量备份时,
INCREMENTAL FROM SCN填的是主库当前的SCN而非备库的SCN,这样生成的备份包含的变化范围完全不对,备库恢复时会报错。用备库SCN的前提是备库当前没有未应用的日志,如果备库有接收但未应用的日志,取SCN时需要选择最老的那个未应用日志对应的SCN。坑三:传备份文件时中断。用scp传大文件本身没问题,但如果网络不稳定,中断重传很浪费时间。建议用rsync搭配断点续传参数,或者先压缩再传输,同时用tmux之类的会话保活工具执行长任务,避免终端断开导致整个过程前功尽弃。
坑四:主库还有日志,但备库注册时路径写错。尤其主备库目录结构不一致时,手动注册归档日志容易把路径写错。检查时用
V$ARCHIVED_LOG里的NAME字段确认一下实际路径,不要凭记忆写路径。坑五:备库只读打开状态。ADG备库可能以只读方式打开,此时如果直接ALTER DATABASE RECOVER MANAGED STANDBY DATABASE会报错,需要先关闭数据库再以mount状态启动,然后启动MRP。有些同事在这个环节反复出错,就是因为备库一直处于只读打开状态。
坑六:视图信息不够全面时,直接查控制文件。如果V$ARCHIVE_GAP查不出来,但你又确实怀疑有Gap,可以查备库控制文件里记录的归档日志最大序号和数据文件里的最大SCN,两者一对比就知道差距了。
4. 我知道你还会问:Gap修复后影响备库只读查询吗
处理完Gap之后,备库如果原本是以ADG只读方式打开的,你需要重新激活这个状态。修复过程和RMAN恢复操作会把备库切换到mount状态,这是正常的,重新打开只读模式即可:
ALTER DATABASE OPEN;这里要注意一个逻辑顺序:先完成所有恢复操作、启动MRP追平日志、确认无Gap之后,最后再打开只读。如果你在MRP启动前就打开备库,MRP会停止,又得重新来过。
另外一个常见疑问是:Gap修复期间,主库业务是否需要暂停。答案是全程不需要。RMAN增量备份是在线备份,不影响主库正常写入;scp传输备份文件走的是单独的网络通道;备库应用增量备份时也不影响主库。整个修复过程对主库业务是透明的。
如果你问我修复一次通常要多久,以我处理过的场景来看,500GB数据量、增量变化大约10GB的情况下,从定位到追平一般不超过一小时。真正耗时的大头是数据传输和增量备份的执行时间,只要网络稳定,速度不会差。
5. 一个更省力的办法:提前准备一份“DG运维速查手册”
处理这类问题这么多回,一个特别实在的建议:趁环境稳定的时候,把常用SQL、备份命令、恢复步骤整理成速查手册,放在某一个固定的wiki或者共享文档里。这样真正出问题的时候,你不需要临时回忆命令,直接按手册操作就行。
速查手册至少应该包含这几块内容:主备库的连接方式和账号、查询Gap的标准SQL、RMAN增量备份与恢复命令模板、手动注册归档日志的脚本、常见报错及对应处理办法。手册里还可以记录环境特定的信息,比如主备库的DB_UNIQUE_NAME、归档目录路径、FAL参数配置,这些信息平时看着不起眼,故障时却能省下很多排查时间。
我个人在实际操作中的一个习惯是:每次处理完Gap事故,都会把这次的操作过程、遇到的报错和解决方式追加到手册里,加上时间戳和备注。时间一长,这本手册就成了团队里价值很高的故障处理参考。某次某同事遇到一个类似的Gap问题,打开手册发现我半年前已经记录过一模一样的报错,按步骤处理,几分钟就解决了。
Oracle ADG的Gap问题其实并不可怕,熟悉了定位方法和恢复流程,绝大多数场景都能在一个小时内妥善解决。就怕平时不查、发现不及时,最后把自己逼到重建备库的地步。希望你处理完这次之后,把巡检脚本和监控告警一起配好,让Gap没有机会重新找上门。