Data Guard 归档损坏,根因竟是磁盘写满

做 Data Guard 久了,归档缺失其实并不可怕,最怕的是归档文件明明存在,控制文件中也有对应记录,MRP 应用到这里时却突然告诉你日志已经损坏。缺失的归档可以从主库重新传输,也可以从备份中恢复,但归档损坏往往会把排查方向带到网络、存储、文件系统甚至 Oracle Bug 上,如果只把损坏文件替换掉而没有继续追查根因,那么同样的问题很可能还会再次发生。

最近在一套 Oracle 11.2.0.4 RAC Data Guard 环境中,我们就遇到了一次比较典型的归档损坏故障。最开始只是 Thread 2 的一个归档日志应用失败,随后陆续发现多个不连续的归档文件存在损坏。经过 RMAN 校验、Veeam 备份恢复、文件二进制对比、XFS Extent 分析以及 Alert Log 和 RFS Trace 交叉验证,最终发现这些归档并不是被存储随机写坏,也不是网络传输过程中发生了普通的数据篡改,而是在备库磁盘空间耗尽以后,RFS 接收到的部分归档形成了逻辑大小正常、内部却存在大量空洞的稀疏文件。

更值得关注的是,磁盘空间在第二天中午释放以后,Data Guard 自动补齐了大部分真正缺失的归档,却没有重新获取这些已经登记但内容不完整的文件,直到几天后 MRP 应用到对应 Sequence,问题才通过 ORA-00353 和 ORA-00354 暴露出来。

本文中的数据库名称、主机名、备份标识等信息均已脱敏。

故障从一个损坏归档开始

故障发生时,备库 Alert Log 中首先出现了下面这组错误:

Media Recovery Log /data/arch/2_416411_1158707423.arc

Incomplete read from log member
'/data/arch/2_416411_1158707423.arc'. Trying next member.

ORA-00353: log corruption near block 2056
ORA-00334: archived log:
'/data/arch/2_416411_1158707423.arc'

ORA-00354: corrupt redo log block header

MRP 检测到归档损坏以后中断了日志应用,并尝试重新获取 Thread 2、Sequence 416411,但 FAL 请求最终失败:

Media Recovery Waiting for thread 2 sequence 416411
Fetching gap sequence in thread 2, gap sequence 416411-416411

FAL[client]: Failed to request gap sequence
GAP - thread 2 sequence 416411-416411
FAL[client]: All defined FAL servers have been attempted.

从报错来看,问题并不复杂:备库上的 2_416411_1158707423.arc 存在损坏,Oracle 尝试重新获取却没有成功。主库查询 V$ARCHIVED_LOG 后发现,本地归档记录已经被删除,但对应 Sequence 仍然保留在 Veeam RMAN 备份中,因此可以直接从 SBT 备份恢复。

由于环境使用的是 Veeam Plug-in for Oracle RMAN,恢复时除了分配 SBT_TAPE 通道,还需要指定插件库和 Veeam 返回的 srcBackup。实际恢复命令如下:

RUN {
    ALLOCATE CHANNEL VeeamAgentChannel1 DEVICE TYPE SBT_TAPE
      PARMS 'SBT_LIBRARY=/opt/veeam/VeeamPluginforOracleRMAN/libOracleRMANPlugin.so';

    SEND 'srcBackup=<VEEAM_BACKUP_ID>';

    SET ARCHIVELOG DESTINATION TO '/tmp/arch_restore';

    RESTORE ARCHIVELOG SEQUENCE 416411 THREAD 2;

    RELEASE CHANNEL VeeamAgentChannel1;
}

归档成功恢复以后,将文件传输到备库,替换原来的损坏文件,再重新启动 MRP。原本以为问题到这里就结束了,但 MRP 刚刚继续向后应用,很快又在 Thread 2、Sequence 416413 上报了完全相同的错误。

这说明问题不是某一个归档偶然损坏,而是后面可能还存在其他同类文件。

修好一个归档,又发现了更多损坏文件

为了避免 MRP 每遇到一个坏归档就停一次,我们没有继续采用“遇到一个修一个”的方式,而是直接对尚未应用的归档范围执行 RMAN VALIDATE。

当时备库已经接收到的最大序列分别为:

Thread 1:425712
Thread 2:420300

已应用序列分别停留在:

Thread 1:421807
Thread 2:416412

也就是说,两个线程合计还有接近八千个归档等待应用。为了降低一次性扫描对磁盘 I/O 的影响,我们将每个线程按一百个 Sequence 一组分批校验,最终发现以下八个归档存在损坏:

Thread Sequence
1 421810
1 423155
1 425677
2 416413
2 416419
2 417649
2 418949
2 420226

再加上最开始已经修复的 Thread 2、Sequence 416411,这次事故实际上共确认了九个损坏归档。好在校验日志中没有发现文件缺失,除这些损坏文件以外,其余未应用归档均能够正常通过 RMAN 校验。

其中一个典型的 RMAN 校验结果如下:

Thrd Seq     Status Blocks Failing Blocks Examined Name
---- ------- ------ -------------- --------------- ----------------------------
2    416413  FAILED 12281          12319
/data/arch/2_416413_1158707423.arc

2    416419  FAILED 20415          20422
/data/arch/2_416419_1158707423.arc

2    417649  FAILED 18158          20213
/data/arch/2_417649_1158707423.arc

从失败块数量来看,这些文件并不是只有一两个 redo block 损坏。例如 Sequence 416413 一共检查了 12319 个块,其中 12281 个块失败,说明文件绝大部分内容已经不可用。

先把 Data Guard 恢复起来

确认损坏范围以后,下一步是为每个归档寻找可靠副本。大部分归档可以从 Veeam SBT 备份恢复,少数较新的归档尚未进入备份,但主库 ASM 中的原始文件仍然存在,因此可以通过 BACKUP AS COPY 导出到普通文件系统。

Veeam 中有备份的归档,可以在同一个 RMAN RUN 块中分别恢复:

RUN {
    ALLOCATE CHANNEL VeeamAgentChannel1 DEVICE TYPE SBT_TAPE
      PARMS 'SBT_LIBRARY=/opt/veeam/VeeamPluginforOracleRMAN/libOracleRMANPlugin.so';

    SEND 'srcBackup=<VEEAM_BACKUP_ID>';

    SET ARCHIVELOG DESTINATION TO '/tmp/arch_restore_corrupt';

    RESTORE ARCHIVELOG SEQUENCE 421810 THREAD 1;
    RESTORE ARCHIVELOG SEQUENCE 423155 THREAD 1;

    RESTORE ARCHIVELOG SEQUENCE 416413 THREAD 2;
    RESTORE ARCHIVELOG SEQUENCE 416419 THREAD 2;
    RESTORE ARCHIVELOG SEQUENCE 417649 THREAD 2;

    RELEASE CHANNEL VeeamAgentChannel1;
}

仍然保留在主库 ASM 中的归档,则使用下面的方式导出:

RUN {
    ALLOCATE CHANNEL d1 DEVICE TYPE DISK;

    BACKUP AS COPY
      ARCHIVELOG SEQUENCE 425677 THREAD 1
      FORMAT '/tmp/arch_restore_corrupt/1_425677_1158707423.arc';

    BACKUP AS COPY
      ARCHIVELOG SEQUENCE 418949 THREAD 2
      FORMAT '/tmp/arch_restore_corrupt/2_418949_1158707423.arc';

    BACKUP AS COPY
      ARCHIVELOG SEQUENCE 420226 THREAD 2
      FORMAT '/tmp/arch_restore_corrupt/2_420226_1158707423.arc';

    RELEASE CHANNEL d1;
}

所有归档准备完成以后,先计算 MD5,再传输到备库临时目录进行二次校验。确认文件大小和 MD5 完全一致后,停止 MRP,将原损坏文件移入隔离目录,再把恢复出来的正常文件放回 /data/arch

替换完成以后,对所有文件重新执行 RMAN VALIDATE,结果全部变成:

Status: OK
Failing Blocks: 0

到这里,文件层面的修复已经完成,但重新启动 MRP 时又出现了一个很容易被忽略的问题。

修复过程中的第二个坑:MRP 又读到了隔离目录中的坏文件

为了保留现场,我们将原来的损坏文件移动到了:

/data/arch/corrupt_20260727

新文件已经放回正式目录并通过 RMAN 校验,但 MRP 启动以后,Alert Log 中读取的却不是正式目录中的文件,而是隔离目录中的旧文件:

Media Recovery Log
/data/arch/corrupt_20260727/2_416413_1158707423.arc

随后再次报出 ORA-00353 和 ORA-00354。

查询 V$ARCHIVED_LOG 后发现,同一个 Thread 和 Sequence 存在两条记录,一条指向正式目录,REGISTRAR=RFS;另一条指向隔离目录,REGISTRAR=SRMN。也就是说,隔离目录中的坏文件后来又被 RMAN 登记到了控制文件中,MRP 在选择归档副本时恰好选中了这份损坏文件。

处理方式是先停止 MRP,然后取消登记隔离目录中的所有归档:

CHANGE ARCHIVELOG
LIKE '/data/arch/corrupt_20260727/%'
UNCATALOG;

RMAN 成功取消登记了八个隔离文件,随后再次执行 LIST ARCHIVELOG LIKE 已经查询不到这些记录。

接着在 SQL*Plus 中对当前最早需要应用的正常归档执行强制替换登记:

ALTER DATABASE REGISTER OR REPLACE PHYSICAL LOGFILE
'/data/arch/1_421808_1158707423.arc';

ALTER DATABASE REGISTER OR REPLACE PHYSICAL LOGFILE
'/data/arch/2_416413_1158707423.arc';

再次启动 MRP 后,Alert Log 中终于开始读取正式目录中的文件:

Media Recovery Log /data/arch/1_421808_1158707423.arc
Media Recovery Log /data/arch/2_416413_1158707423.arc
Media Recovery Log /data/arch/1_421809_1158707423.arc
Media Recovery Log /data/arch/1_421810_1158707423.arc
Media Recovery Log /data/arch/2_416414_1158707423.arc

此时 MRP 状态恢复为 APPLYING_LOGGAP_STATUS 变成 NO GAP,Transport Lag 回到 0,Apply Lag 也开始持续下降。Data Guard 已经恢复,但真正有价值的排查其实才刚刚开始,因为我们还没有解释清楚:这些归档为什么会损坏?

文件大小完全一致,内容为什么会损坏

最初排查根因时,网络、VMware、XFS、PVSCSI 和 Oracle Bug 都在怀疑范围内。备库使用 XFS 文件系统,底层是由三个 VMware Virtual Disk 组成的普通 LVM Linear Volume;操作系统 dmesg 中没有发现 XFS shutdown、SCSI timeout、PVSCSI reset 或 Buffer I/O Error。网卡 vmxnet3 虽然存在少量接收错误和 driver drop,但发送侧没有异常,FCS 和接收缓冲区分配失败也都是 0。

单纯依靠这些信息,很难判断究竟是网络、存储还是 Oracle 本身的问题。真正的突破口来自坏文件与正常文件的二进制比较。

坏文件和从主库或 Veeam 恢复出来的正常文件,逻辑大小完全相同,但从某些固定位置开始,大量数据出现差异。将文件头后的内容按照 1MB 分块计算 MD5 后,发现很多损坏区间的 MD5 都是:

b6d81b360a5672d80c27430f39153e2c

这个值正是 1MB 全零数据的 MD5。

例如,Thread 2、Sequence 416413 的前五个 1MB Chunk 全部为零,后面的少量数据却又是正常的;Thread 2、Sequence 420226 则是前六个 Chunk 正常,中间四个 Chunk 全零,最后两个 Chunk 又恢复正常。

这些全零区域的起始位置也非常有规律,基本都满足:

4KB + N × 1MB

换算成 Oracle redo block,就是:

Block 8 + N × 2048

这与 Alert Log 中报出的 log corruption near block 8near block 2056near block 6152 等位置完全对应。很显然,这不是普通的随机比特翻转,也不像磁盘坏块造成的零散损坏,而是某些以 1MB 为单位的数据区间从一开始就没有写进去。

filefrag 给出了决定性证据

为了确认这些全零内容到底是被写成了零,还是根本没有写入,我们进一步使用 filefragstatdu 检查文件的物理 Extent。

以 Thread 2、Sequence 416413 为例,这个文件的逻辑大小是:

6307840 字节,约 6.1MB

stat 显示它实际只分配了 40 个 512 字节块,du 查看实际磁盘占用只有 20KB,而 du --apparent-size 显示的逻辑大小仍然是 6.1MB。

filefrag 的结果更加直观:

logical 0..0       有物理 extent
logical 1..1535    没有物理 extent
logical 1536..1539 有物理 extent

也就是说,这个逻辑大小为 6.1MB 的文件,真正落到磁盘上的只有文件头和末尾极少数数据,中间几乎全部是文件空洞。

这种文件在 Linux 中叫作 Sparse File,也就是稀疏文件。稀疏文件的逻辑长度可以很大,但中间没有实际分配磁盘块的区域并不占用物理空间。当应用程序读取这些区域时,XFS 不会返回 I/O 错误,而是直接返回全零数据。

至此,问题的性质已经非常清楚:这些归档不是后来被磁盘“写坏了”,而是在生成时就有部分数据根本没有写入。文件路径存在、逻辑大小正常,控制文件中也有记录,但其中某些 redo 数据区间实际上只是 Sparse Hole。

7 月 24 日磁盘写满,把所有证据串了起来

继续检查备库 Alert Log 后,终于发现了决定性的错误:

ORA-19502: write error on file
"/data/usmesdg/tbs_xxx.309.xxxxxxxxxx"

Linux-x86_64 Error: 28: No space left on device

同一时期还出现了:

ORA-16038: log 11 sequence# xxxxxx cannot be archived
ORACLE Instance standby - Archival Error

RFS Trace 中则连续出现:

Archivelog creation failed; error 19504

这说明事故发生时,备库 /data 文件系统确实已经没有可用空间,RFS 创建归档文件的过程也明确发生了失败。到这里,归档损坏、Sparse Hole 和磁盘写满之间的证据链已经完整闭环。

从文件表现来看,RFS 接收归档时留下了完整的逻辑文件长度,但部分以 1MB 为单位的数据区间因为空间不足没有真正分配物理块。之后即使还有少量数据成功写入,前面没有写入的区域仍然会保持为空洞,因此才会出现“前面全零、中间正常”或者“前面正常、中间全零、最后又正常”的情况。

值得注意的是,当时还同时出现了:

ORA-19815: db_recovery_file_dest_size ... 100.00% used
ORA-19809

FRA 配额耗尽和 /data 文件系统物理空间耗尽是两个不同的问题。ORA-19815 表示 Oracle 设置的 FRA 配额已经达到上限,而 Linux Error: 28 则表示底层文件系统真正没有了可分配空间。本次归档形成 Sparse Hole 的决定性触发条件,是后者。

为什么磁盘释放以后,坏归档没有自动修复

客户环境中有一个归档清理计划任务,每天中午 12 点执行,删除七天以前的归档日志。查询损坏归档和相邻 Sequence 的完成时间以后,发现时间规律几乎与这个计划任务完全对应。

以 7 月 24 日为例,最初损坏的归档在当天 17:29 至 17:37 之间由 ARCH 发送、RFS 接收并登记;但周围大量正常归档直到第二天中午 12:02 至 12:05 才集中完成接收。类似的情况在 7 月 25 日、26 日和 27 日也重复出现:傍晚产生异常归档,第二天中午清理任务释放空间以后,相邻缺失日志开始批量补传。

整个过程实际上是这样的:

/data 文件系统写满
        ↓
RFS 接收归档时部分写入失败
        ↓
留下逻辑大小完整的稀疏归档
        ↓
控制文件中已经存在该 Sequence 的记录
        ↓
第二天 12 点清理旧归档,释放空间
        ↓
ARCH/FAL 补传真正缺失的 Sequence
        ↓
已经存在并已登记的坏归档没有重新获取
        ↓
几天后 MRP 应用到该文件
        ↓
读取 Sparse Hole 得到全零 redo block
        ↓
ORA-00353 / ORA-00354

这也解释了为什么问题没有在磁盘写满当天立即以 MRP 中断的形式暴露出来。当时备库存在较大的 Apply Lag,MRP 还没有应用到这些 Sequence。对于 Data Guard 来说,完全不存在的归档属于 Gap,可以通过 ARCH/FAL 重新获取;但这些损坏归档的文件路径存在,控制文件也已经登记,逻辑大小看起来没有异常,因此不会被当成缺失文件重新传输。只有 MRP 真正读取 redo block 时,Oracle 才发现内部内容不合法。

归档量并不算夸张,为什么 1.4TB 还是写满了

统计最近几天的归档产生量后发现,正常情况下每天大约产生 26GB 至 30GB 的归档:

日期 归档量 文件数量
2026-07-21 26.24GB 2386
2026-07-22 26.04GB 2372
2026-07-23 25.77GB 2365
2026-07-25 27.86GB 2591
2026-07-26 27.99GB 2594
2026-07-27 29.52GB 2747

按照每天 30GB、保留七天计算,归档本身大约需要 210GB。表面上看,/data 总容量达到 1.4TB,似乎不应该因为七天归档就被写满。

但问题在于,/data 并不是专门的归档文件系统。从 ORA-19502 的报错路径可以看出,备库数据文件同样位于 /data,归档日志、数据文件以及其他临时文件共同使用同一个文件系统。只要数据文件发生扩展、Apply Lag 造成归档积压,或者临时恢复文件没有及时清理,就可能迅速压缩归档可用空间。

因此,本次事件不能简单归结为“归档清理任务没有执行”,更准确的说法是:数据文件和归档共用空间,容量缺乏隔离;清理任务每天只执行一次,没有磁盘阈值保护;磁盘在中午释放空间以后,到了傍晚又可能重新接近满空间,从而连续多天产生新的不完整归档。

故障最终是如何恢复的

整个恢复过程可以归纳为以下几个步骤。

首先停止 MRP,对所有未应用归档分批执行 RMAN VALIDATE,准确识别损坏范围,而不是每遇到一个报错再处理一个。随后根据每个归档的保存情况,从 Veeam SBT 备份或主库 ASM 中恢复正常副本,并通过 MD5 和 RMAN VALIDATE 双重确认文件完整性。

替换备库中的损坏文件后,还需要注意清理隔离目录对应的 RMAN 记录,避免 MRP 再次选中旧文件。必要时使用 REGISTER OR REPLACE PHYSICAL LOGFILE 强制刷新正确路径。最后重新启动实时应用,并通过 V$MANAGED_STANDBYV$ARCHIVE_GAPV$DATAGUARD_STATS 验证状态。

恢复完成以后,备库状态如下:

MRP0             APPLYING_LOG
RECOVERY_MODE    MANAGED REAL TIME APPLY
GAP_STATUS       NO GAP
transport lag    +00 00:00:00

Apply Lag 仍然存在,但已经开始持续下降,这属于故障期间积压日志的正常追赶过程。

这次故障留下的几个教训

这次事故最需要整改的并不是某一条 Data Guard 参数,而是整个归档容量管理方式。

首先,备库数据文件与归档日志不应该长期共用同一个文件系统。归档突增不应该影响数据文件写入,数据文件扩展也不应该挤占归档接收空间。更合理的规划是将数据文件和归档拆分到独立文件系统或独立 LVM,给归档目录保留明确的容量边界和安全余量。

其次,归档清理不能只依赖每天固定时间执行一次。每天中午 12 点清理意味着一旦下午或傍晚磁盘写满,系统必须等到第二天中午才能自动释放空间。在高归档量环境中,应该增加磁盘使用率阈值监控,并根据归档备份状态、应用状态和保留要求动态清理。清理操作也不建议长期使用简单的 rm,而应尽量通过 RMAN 管理,避免文件系统与控制文件记录长期不一致。

容量规划也不能只计算“日归档量乘以保留天数”。在 Data Guard 环境中,还要考虑 Apply Lag 积压、网络中断后的 FAL 补传、归档重复接收、故障处理临时文件以及至少 20% 至 30% 的安全余量。按照当前每天约 30GB 的归档量计算,七天基础需求约为 210GB,如果再考虑故障期间数日不能清理和补传空间,独立归档文件系统至少应准备 300GB 至 400GB,实际容量还应结合业务增长趋势继续评估。

另外,只监控磁盘使用率还不够。一旦出现 Linux Error: 28ORA-19502ORA-19504ORA-16038,即使释放空间以后 Data Guard 表面上恢复了传输,也应该立即校验故障时间段内已经接收的归档。因为磁盘满可能留下“文件存在但内部为空洞”的特殊情况,单纯查询 V$ARCHIVE_GAP 并不能发现这种问题。

可以对新接收归档增加简单的稀疏文件扫描:

find /data/arch -maxdepth 1 -type f -name '*.arc' -mmin -60 |
while read f
do
    size=$(stat -c %s "$f")
    allocated=$(( $(stat -c %b "$f") * 512 ))

    if [ "$allocated" -lt "$size" ]; then
        echo "SPARSE ARCHIVE: $f size=$size allocated=$allocated"
    fi
done

正常归档文件的实际分配空间应该与逻辑大小基本一致。如果某个归档逻辑大小为十几 MB,实际占用却只有几十 KB,就需要立即停止应用、隔离文件并重新补传。

总结

回顾整个过程,这次故障表面上是一个普通的 ORA-00353 归档损坏问题,但真正的根因并不在 MRP,也不在主库 ASM、Veeam 备份、网络或者 XFS 文件系统本身,而是备库 /data 文件系统空间耗尽以后,RFS 接收的部分归档形成了稀疏文件。文件逻辑长度正常,控制文件中也有有效记录,但部分以 1MB 为单位的 redo 数据区间没有真正落盘,读取时只能得到全零内容。

每天中午的清理任务释放空间后,Data Guard 自动补齐了真正缺失的归档,却没有重新获取这些已经登记的损坏文件,因此问题一直潜伏到 MRP 应用对应 Sequence 时才最终暴露。

这次排查最有价值的地方,不是掌握了如何从 Veeam 恢复归档,也不是知道了如何重新注册日志,而是通过 RMAN VALIDATE、二进制分块比对、filefragstat、Alert Log 和 RFS Trace,把“归档损坏”还原成了一个完整、可解释的故障链路。

很多时候,磁盘写满并不一定只表现为文件创建失败,它还可能留下一个看起来大小正常、实际上大部分内容根本没有写进去的文件。对于 Data Guard 来说,这种文件比直接缺失更加隐蔽,因为它不会立即形成可见的 Gap,直到 MRP 真正读取时才会报错。故障恢复只是把备库重新拉起来,能够解释它为什么发生、为什么几天后才暴露,以及如何避免再次发生,才是这次事故真正的价值。

posted @ 2026-07-29 13:14  三笠丶丶  阅读(2)  评论(0)    收藏  举报