
凌晨1点40分电话响起来的时候我就知道多半是Oracle又出事了。运维同事说生产库连不上了应用连接池报错登录服务器一看ORA-00257archiver error. Connect internal only, until freed. 归档日志目录满了数据库直接进入只读挂起状态。这套库跑了三年归档日志平时没人管结果当天晚上一张批量任务把日志量顶起来瞬间把最后一点空间吃完了。这种事故我相信不少DBA都遇到过。大多数Oracle生产库都开着归档模式而归档日志就像家里不断堆积的旧报纸——你总觉得哪天会用上但从来没想过它会把客厅淹没。我写这篇文章就是想把归档日志清理这件事彻底讲透为什么要定期清、正确的清理姿势是什么、哪些时刻一根手指都不能碰日志。不管是专职DBA还是需要兼顾数据库的开发和运维消化完这篇文章你应该能独立设计出一套适合自己环境的归档清理方案。1. 为什么DBA总在半夜被归档日志叫醒1.1 归档日志是怎么一步步把磁盘吃满的想搞清楚清理的必要性先得明白归档日志从哪里来、为什么永远涨不停。Oracle的redo log是循环写入的一组redolog写满了就切换下一组切换时触发的LGWR写SCN、控制文件更新等机制我就不展开了关键点是在ARCHIVELOG模式下每当一组redo log切换后台的ARCH进程会把已经写满的那一组redo log完整复制一份生成一个归档日志文件。这个文件通常被放到快速恢复区Flash Recovery Area或者指定的LOG_ARCHIVE_DEST目录里。打个比方redo log是写字台上的便签本写满一页就得翻页归档日志就是每翻一页之前先把这一页用复印机复印一份存进档案柜。问题是这个档案柜不会自动清理每天都复印几页到几十页出来只要数据库有事务在跑归档日志的产生就没有尽头。那么问题来了每天产生的归档量有多大我遇到过的库少的每天几个G高峰期一天几百G的也有。你可以用这条SQL看一眼自己库最近7天每天产生了多少归档SELECT TO_CHAR(COMPLETION_TIME, YYYY-MM-DD) day, ROUND(SUM(BLOCKS * BLOCK_SIZE) / 1024 / 1024 / 1024, 2) size_gb, COUNT(*) cnt FROM V$ARCHIVED_LOG WHERE COMPLETION_TIME SYSDATE - 7 GROUP BY TO_CHAR(COMPLETION_TIME, YYYY-MM-DD) ORDER BY 1 DESC;很多库一开归档模式就是好几年从头到尾没人定过保留策略。你想想如果每天产生20G归档连续跑100天就是2T。快速恢复区才多大一般生产库撑死了配2到4T。一旦写满数据库就进入我们开头说的挂起状态因为LGWR没法切换日志所有DML操作全部卡住业务直接停摆。1.2 不清理的代价从告警邮件到生产事故很多人以为归档目录满了顶多是“新归档日志写不进来”数据库还能继续跑。这是最大的误解。ORA-00257一出现整个数据库是全局挂起的不是只影响归档功能。因为LGWR无法完成日志切换commit都提交不了所有会话都会卡在等待事件Log File Switch (Archiving Needed)上。这种事故的影响严重程度和你业务高峰期直接挂钩。半夜发书还好要是白天业务高峰遇到全公司都能感受到“数据库卡死了”。我见过最惨的一次案例某系统在月底结账当天归档日志把磁盘打满数据库挂起接近两小时事后复盘发现这个库已经连续三个月没有做任何归档清理只是平时业务量小没到临界值月底一冲量问题立刻爆发。除了直接挂库归档日志长期不清理还会带来第二轮连锁反应RMAN备份失败。备份时archivelog备份完会直接写入备份集但日志体积膨胀后磁盘空间不足备份任务失败。而备份失败意味着你更不敢删归档日志——因为依赖这些日志做恢复。于是陷入“日志占满空间→备份没有空间→不敢清理→空间更紧张”的死循环。所以你会发现归档日志清理不是一个“可做可不做”的维护项而是一个生产数据库的基本卫生习惯。定期清理是保证数据库不会因为空间问题而暴毙的最简单手段。2. 动手清理之前先回答这4个问题2.1 现在的数据库到底处在什么状态清理归档日志不是上来就敲一条RMAN命令而是要先搞清楚自己的环境。很多线上事故就是因为操作者对环境一无所知盲目执行了一个“网上看来的删除命令”。第一个要确认的数据库确实处于ARCHIVELOG模式而不是NOARCHIVELOG模式。用下面的命令看一眼ARCHIVE LOG LIST;或者SELECT LOG_MODE FROM V$DATABASE;第二个要确认的归档日志存在哪里快速恢复区的配额是多少。执行SELECT NAME, SPACE_LIMIT/1024/1024/1024 limit_gb, SPACE_USED/1024/1024/1024 used_gb, SPACE_RECLAIMABLE/1024/1024/1024 reclaim_gb FROM V$RECOVERY_FILE_DEST;第三个要确认的这套环境是单机、RAC、还是配置了Data Guard备库有没有下游系统通过归档日志做同步比如OGG、分析系统拉取日志这些信息决定了你能删多少、不能删多少。我见过有人在一个有备库的环境里直接在主库上把14天前的归档全删了结果备库因为网络抖动还没拉完DG直接中断最后只能重建备库那叫一个惨。2.2 这份ArchiveLog还能不能安全删除在RMAN里删除归档日志本质上是在告诉数据库“这些日志以后不会再用来做恢复了。”所以“能删”的定义是这些日志不再被任何恢复场景依赖。怎么判断最简单的逻辑保留一个能满足恢复需求的“安全窗口”。如果你们的备份策略是全备每周日、增量每天一次恢复目标是可以恢复到最近任意一天那么保守做法是保留最近7天的归档日志更早的可以删除。这个“7天”不是拍脑袋定的而是和全备周期、RPO要求对齐如果全备失败你还要依赖最近一次全备之后的增量归档来恢复。如果业务对恢复窗口有硬性要求比如“至少要能恢复到14天前”那么你的归档保留时间就不应该低于14天。要记住保留策略要服从业务需求DBA不要自己替业务做决定。在清理前我建议先执行一下这个查询看看目前归档日志的时间跨度和总量SELECT MIN(COMPLETION_TIME) oldest_arch, MAX(COMPLETION_TIME) newest_arch, COUNT(*) arch_cnt, ROUND(SUM(BLOCKS * BLOCK_SIZE) / 1024 / 1024 / 1024, 2) size_gb FROM V$ARCHIVED_LOG WHERE STATUS A;STATUSA表示可用Available也就是控制文件里还能找到的归档日志记录。清掉后这些记录在控制文件里也会被标记删除。2.3 备库、容灾、下游同步都在正常消费吗这是最容易出问题的一环。很多人清理归档日志时只看主库忽略了“消费端”。如果有Data Guard备库备库需要从主库拿到归档日志做redo apply。如果主库清理时备库还没应用完成的日志被删了那备库就断了。断了的备库虽然可以重新补日志但补日志的过程很痛苦尤其是在网络带宽有限的情况下。所以清理前建议先确认备库的应用状态。在备库上执行SELECT THREAD#, MAX(SEQUENCE#) applied_seq FROM V$ARCHIVED_LOG WHERE APPLIED YES GROUP BY THREAD#;如果在主库上查询可以看V$ARCHIVE_DEST_STATUSSELECT DEST_NAME, STATUS, ERROR, TYPE FROM V$ARCHIVE_DEST_STATUS WHERE STATUS ! INACTIVE;正常情况下备库的应用延迟应该在几秒到几分钟之间。如果发现备库的APPLIED_SEQ远小于主库当前序列说明延迟比较大此时不要急着清理先把延迟降下来再说。另外如果有OGG或者别的系统拉取归档日志做解析同样要确认这些下游系统的消费进度。一旦你把它们还没消费的日志删掉数据流断掉补数据的工作量比清理省下的那点空间大得多。2.4 当前保留窗口和备份策略是多少归档日志的保留窗口应该是备份策略的“影子”备份决定你能恢复到哪里归档决定你能恢复到多细的粒度。常见的搭配策略可以参考下面这张表备份策略归档保留建议适用场景每周日全备 每日增量保留7天中小型业务可接受最多丢失一天数据每日全备保留2~3天业务较关键要求恢复点RPO较短全备 DG备库主库保留2~3天备库视应用速度保留5天以上核心系统需要容灾和快速切换每天全备 每日多次增量保留1天即可但建议多留1天缓冲恢复点要求非常高的系统在实际操作中我偏向于把归档保留时间适当放宽比如策略要求7天我保留10天。磁盘空间贵但一套备份数据链断了更贵。宁可每天多占两个G也不要在关键时刻发现日志已经被删到补不回来。确认备份策略后还要用RMAN查一下最近备份有没有成功。清理前如果发现最近一次全备或增量备份失败了那归档日志最好暂时先不动等备份正常跑过一遍再清理。rman target / EOF LIST BACKUP SUMMARY; EOF看输出里TYPE为DB FULL、INCR的备份记录确认最近一条的状态是“A”Available而不是“X”Expired。3. 什么时候绝对不能碰归档日志3.1 备库尚未应用完日志时前面提到过备库应用延迟大的时候不要清理。这里我想强调力度哪怕延迟只有一分钟在极端情况下你删了主库归档日志而备库那一分钟内的日志还没传完整个DG链路就可能中断。实际工作中我遇到过一次主库和备库在同一机房平时延迟几乎为零。某天主库跑批事务量暴涨备库应用跟不上延迟爬到了十几分钟。而我当时没注意到这个延迟按计划执行了清理脚本结果备库应用到一半时发现日志断了两边的序列号对不上DG直接broker报ERROR。最后我只好从主库把缺失的归档日志SCP到备库手工注册后才恢复。所以这里的铁律是备库最大已应用序号之后的日志一律不删。如果你不确定备库状态宁可不清理也别为了省空间去赌一把。3.2 最后一次有效备份还依赖这些日志时有一种情况很隐蔽你已经做了全备但全备完成的那一瞬间控制文件里记录的检查点SCN之前还有一些归档日志这些日志是保证备份一致性的最后一道防线。正常来说只要你全备成功并且清理时保留了最近的归档不会出问题。但如果你的清理脚本是一条“DELETE ARCHIVELOG BEFORE SYSDATE-14”的固定任务而全备最近几天接连失败那就危险了14天前的归档删了最近没有有效的全备需要一个多月前的归档来做恢复结果已经找不回来了。我的处理习惯是清理脚本里先判断最近一次RMAN备份是否成功备份成功才允许清理。判断方式可以在Shell里过滤RMAN日志也可以直接查询SELECT SESSION_KEY, INPUT_TYPE, STATUS, TO_CHAR(START_TIME, YYYY-MM-DD HH24:MI) start_time FROM V$RMAN_BACKUP_JOB_DETAILS WHERE INPUT_TYPE IN (DB FULL, INCR) AND START_TIME SYSDATE - 3 ORDER BY START_TIME DESC;当然你用的是备份一体机或者第三方备份工具那就以你工具的备份日志为准。原则就一条没有有效备份的日子里归档日志千万手下留情。3.3 执行过不完全恢复或有多Incarnation时Incarnation这个概念可能很多新手DBA没怎么接触过但一旦遇到就非常容易踩坑。简单说数据库每次执行不完全恢复比如恢复到昨天某个时间点都会开启一个新的“时间线版本”。旧时间线的日志和新的时间线是分叉的。如果你只按时间条件清理比如“删除所有7天前的日志”有可能把旧时间线里还能用来做闪回恢复的关键日志也给删了。这种场景确实少见但我遇到过一次某库误删数据DBA做了时间点恢复恢复了数据。结果两周后业务反馈说还有另一笔数据也需要找回来要恢复到两周前的另一个时间点。这时候只能依赖旧Incarnation的归档日志但很不巧清理任务已经把旧时间线的归档清掉了最终只恢复出来一部分。所以如果你管过做过不完全恢复的库或者查询V$DATABASE_INCARNATION发现有多个时间线我的建议是所有旧时间线的归档日志在确认业务绝对不会再追溯历史之前都不删除。宁可多占空间也不要把自己推到“历史无法追溯”的绝境。3.4 RAC环境不确认全局就动手时RAC环境下的归档日志按THREAD区分节点1的日志线程是1节点2的日志线程是2序列号是各自独立的。这意味着如果你想清理某个节点之前的日志不能简单地按“删除所有早于某个时间的日志”而是要按照线程维度和序列号去删。否则可能出现节点2的日志已经应用完了但节点1还有日志正在被某个节点上的数据库会话长时间占用你一把梭全删了节点1那边恢复直接缺日志。在RAC环境我建议先分别查看每个thread的最新archived log序列SELECT THREAD#, MAX(SEQUENCE#) max_seq FROM V$ARCHIVED_LOG WHERE STATUS A GROUP BY THREAD#;然后RMAN删除时每条THREAD单独指定UNIL SEQUENCErman target / EOF DELETE NOPROMPT ARCHIVELOG UNTIL SEQUENCE 12345 THREAD 1; DELETE NOPROMPT ARCHIVELOG UNTIL SEQUENCE 23456 THREAD 2; EOF这样至少能确保每个线程的日志不会被误删成“另一个节点还在用我被删了”的尴尬局面。4. 归档日志清理的三种主流方式4.1 用RMAN删除最安全、最推荐日常清理归档日志我99%的情况都用RMAN。它最大的优势在于RMAN删除归档日志时会同步清理控制文件里的对应记录保证文件系统和控制文件一致不会出现“日志文件没了控制文件还记着”的脏数据状态。几个最常用的命令我一个个说清楚# 删除所有早于7天前的归档日志 rman target / EOF DELETE NOPROMPT ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-7; EOF这条命令的语义是“删除完成时间在7天前之前的全部归档日志”。注意COMPLETED BEFORE后面跟的是时间条件不是序列号条件。执行时RMAN会扫描控制文件对比每个归档日志的COMPLETION_TIME符合条件就删除。如果你需要更精确地控制比如按序列号删rman target / EOF DELETE NOPROMPT ARCHIVELOG UNTIL SEQUENCE 12345 THREAD 1; EOF这条命令对RAC环境尤其有用可以针对某个线程删除到指定序列号。还有一种场景你希望删除那些已经备份过的归档日志。RMAN提供了一种“先备份后删除”的原子操作rman target / EOF BACKUP ARCHIVELOG ALL DELETE INPUT; EOF这条命令会先备份所有归档日志备份成功后就删除原日志。如果你把归档日志备份到磁盘备份目录或者磁带用这条命令非常顺手等于用备份文件代替了原始日志既保留了恢复能力又腾出了空间。无论用哪条命令我都要多提一嘴DELETE ARCHIVELOG出来的动作是物理删除控制文件同步删除是不可逆的。动手之前最好先跑一次不带删除的查看命令确认范围rman target / EOF LIST ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-7; EOF确认列出的日志清单符合预期再执行删除。4.2 CROSSCHECK处理物理文件与控制记录不一致长时间跑生产库难免会出现物理文件和RMAN控制记录不一致的情况。比如外部备份软件把归档日志挪走了或者之前有人手动清理过文件没有同步RMAN。这时候RMAN里看到的归档日志记录还在但物理文件已经不存在了删除操作会报错RMAN-06207: WARNING: 2 objects could not be deleted for space management reasons.解决办法是先执行交叉校验让RMAN比对控制文件记录和实际物理文件rman target / EOF CROSSCHECK ARCHIVELOG ALL; DELETE NOPROMPT EXPIRED ARCHIVELOG ALL; EOFCROSSCHECK会把那些“控制文件有记录但物理文件不存在”的日志标记为EXPIRED然后再执行DELETE EXPIRED清理这些失效记录。注意一点CROSSCHECK不会删除任何有效日志它只是做个核对把不一致的记录标记出来。所以这个操作是安全的放心执行。我建议在归档清理脚本里把CROSSCHECK加上特别是如果你的环境中存在多种备份工具并行操作的情况。每次删除前先交叉校验一遍可以避免很多“删不掉”的诡异问题。4.3 手工物理删除的坑为什么我不推荐我知道有人会图省事直接写一条find /u01/archivelog -mtime 7 -exec rm {} \;的Shell命令把7天前的归档文件物理删了。这种操作方式我用一句话评价看起来快后患无穷。手工物理删除有三个问题第一个问题是控制文件失同步。RMAN控制文件里仍然记录着那些归档日志的信息它会认为日志还在。后续RMAN删除时就会因为找不到物理文件而报错或者备份时出现“archived log not found”的警告。第二个问题是没法感知消费端状态。手工删除没有任何逻辑判断它不会管备库是否已经应用、下游系统是否已消费。只要时间条件满足就删这恰恰是最容易出事的场景。第三个问题是最隐蔽的归档文件可能正在被写入中。如果你删的是当前正在归档写入的文件会导致归档日志损坏LGWR或者ARCH进程可能直接会话异常数据库可能直接报错。所以我的建议很明确不要手工物理删除归档日志。RMAN本来就有成熟的删除机制绕开它去用裸命令省不掉几分钟反而埋下一颗雷。5. 生产库归档日志自动化清理脚本5.1 脚本整体设计与关键点说明既然要长期运维靠人手一条条敲RMAN命令不是长久之计。我把平时在生产库上用的清理脚本分享出来你可以根据自己环境调整。先做一个基础版本的脚本思路是删除7天前的归档日志然后做一次CROSSCHECK清理失效记录。#!/bin/bash export ORACLE_SIDorcl export ORACLE_HOME/u01/app/oracle/product/19.0.0/dbhome_1 export PATH$ORACLE_HOME/bin:$PATH DATE$(date %Y%m%d_%H%M%S) LOG_DIR/home/oracle/script/logs RMAN_LOG${LOG_DIR}/clean_arch_${DATE}.log if [ ! -d ${LOG_DIR} ]; then mkdir -p ${LOG_DIR} fi rman target / log${RMAN_LOG} EOF DELETE NOPROMPT ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-7; CROSSCHECK ARCHIVELOG ALL; DELETE NOPROMPT EXPIRED ARCHIVELOG ALL; exit; EOF if [ $? -eq 0 ]; then echo $(date %Y-%m-%d %H:%M:%S) archive clean success ${LOG_DIR}/clean_arch_history.log else echo $(date %Y-%m-%d %H:%M:%S) archive clean FAILED, check ${RMAN_LOG} ${LOG_DIR}/clean_arch_history.log fi这个脚本有几个关键点需要解释RMAN的log参数会把整个执行过程写到日志文件方便你事后排查。脚本最后判断RMAN返回码成功和失败分别写一条历史记录。后面排查时查看clean_arch_history.log就能知道每次清理是否正常。另外我建议在生产环境正式部署前先手工执行RMAN命令但把DELETE换成LIST看看会被删掉的归档日志有哪些确认符合预期后再把DELETE加回去。RMAN支持用LIST命令模拟查看删除范围这是个免费的“预览”功能rman target / EOF LIST ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-7; EOF5.2 部署成定时任务后怎么验证脚本写好了接下来要挂到crontab里定期执行。我一般选择凌晨2点到4点之间的业务低峰时段具体时间根据各库的批处理窗口来定0 2 * * * /home/oracle/script/clean_arch.sh /dev/null 21部署后至少连续观察一周验证这几个维度看一眼RMAN日志确认每天删除的日志数量、释放的空间是否正常。如果某一天删除量为0要检查是不是日志量真的没有还是脚本出了问题。对比删除前后的磁盘空间使用率。这里建议用V$RECOVERY_FILE_DEST里的SPACE_USED做记录而不是只看操作系统df因为快速恢复区可能包含闪回日志等其他文件。最重要的检查备库同步是否受影响。如果清理时间恰好和备库的网络波动重叠可能导致备库延迟。连续观察一周如果备库状态稳定说明脚本的时间窗口是合理的。如果不想每天深夜起来盯日志可以把告警加上脚本执行失败时发送告警邮件或者写入企业微信/钉钉群。这个按你公司的监控体系来不展开了。5.3 配套的空间监控与告警光有清理脚本还不够监控才是兜底措施。清理脚本再完善总有出bug的时候监控能确保你在磁盘打满之前收到预警而不是等到数据库挂起才被动响应。我常用的监控SQL是这一条直接查快速恢复区使用率SELECT ROUND((SPACE_USED / SPACE_LIMIT) * 100, 2) used_pct FROM V$RECOVERY_FILE_DEST;阈值建议使用率超过80%发预警超过90%发严重告警。如果你们有Zabbix、Prometheus这类监控系统把这条SQL包成一个监控脚本每分钟采集一次告警阈值按上面设置可以起到很好的兜底作用。这里提醒一句很多数据库用的不是快速恢复区而是自定义的LOG_ARCHIVE_DEST_n目录。那种情况就要直接用df -h来监控归档目录的磁盘空间使用率而不只是依赖快速恢复区的视图。6. 归档日志相关常见问题速查6.1 ORA-00257数据库挂起怎么救如果运气不好已经遇到了ORA-00257数据库处在挂起状态怎么办按下面的顺序操作能在最短时间内恢复服务第一步确认故障原因SELECT * FROM V$FLASH_RECOVERY_AREA_USAGE;这个视图会显示快速恢复区里归档日志、闪回日志、备份集等各类文件占用的空间。确认归档日志是不是罪魁祸首。第二步检查备库状态。如果环境里有备库先确认备库应用是否正常有没有明显延迟SELECT THREAD#, MAX(SEQUENCE#) applied_seq FROM V$ARCHIVED_LOG WHERE APPLIED YES GROUP BY THREAD#;第三步在业务低峰或者紧急情况下用RMAN清理出足够空间。不需要清太多腾出10%到20%的余量数据库就能自动恢复rman target / EOF DELETE NOPROMPT ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-7; EOF执行完后数据库会自动解除挂起状态因为LGWR可以继续切换日志了。这条路径我走过很多次注意不要在挂库状态下手工删文件RMAN操作会安全得多。6.2 RMAN删完空间没释放怎么回事有时候跑完RMAN删除日志显示删了好几个G但操作系统里看磁盘空间没怎么变这是为什么最常见的情况是文件确实是删除了但空间被其他文件占着。比如快速恢复区里还有大量的闪回日志、或者RMAN的备份集碎片没有清理。快速恢复区是一个混合区域归档日志只是其中一部分。执行下面这条查询看到底是谁在占用空间SELECT FILE_TYPE, PERCENT_SPACE_USED FROM V$FLASH_RECOVERY_AREA_USAGE;另外一种情况如果你用的是ASM磁盘组删除归档文件后空间不会立刻显示释放因为ASM有延迟分配和rebalance机制。通常等一会儿ASM后台把磁盘组的extent重新整理后空间才会逐渐释放。这个等待时间从几分钟到半小时不等。还有一种比较隐蔽的情况某个进程还在持有被删除文件的句柄。比如一个传输工具还在往备库发送归档日志进程一直打开着那个文件即使文件已经被unlink磁盘空间也不会释放直到进程关闭文件句柄。遇到这种只要把进程停掉空间马上回来。6.3 日志切换频率异常暴涨怎么排查有些库归档日志量突然暴涨不是业务真的大了而是日志切换频率异常升高。正常情况下一个生产库的日志切换频率可能在每15到30分钟一次如果变成每1到2分钟一次那就是明显异常。常见原因之一redo log文件组太小日志很快就写满频繁切换。解决办法是增加redo log group的大小或者增加组数。这个操作对生产库可能有短暂影响建议在维护窗口执行。常见原因之二某个应用在循环里频繁commit。比如一个存储过程循环了一百万次每次循环都提交产生大量redo log。这种问题用AWR报告能看出来等待事件里会同时出现Log File Switch (Checkpoint Incomplete)之类的记录。排查方法是查看数据库的告警日志看看切换时间点是否集中在某个时段grep -i switching log $ORACLE_BASE/diag/rdbms/*/*/trace/alert_*.log如果切换集中出现在深夜批处理时段多半是批处理脚本写得不合适如果全天均匀高频切换多半是redo log太小。找到根因再对症下药比盲目加大归档空间靠谱得多。归档日志清理这事儿说难不难说简单但坑很深。我在实际运维里的习惯是先摸清环境再定清理策略然后上脚本自动化最后配好监控兜底。每周抽十分钟看一眼归档日志的增长趋势每个月做一次恢复演练验证备份和日志链路完好。这套流程帮我避开了不少“半夜被叫醒”的场面也希望对你有点帮助。