拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Oracle ORA-00257归档日志满:应急止血、RMAN清理与容量规划

凌晨两点四十分手机在床头柜上震了三下。摸过来一看运维群里已经刷了十几条消息应用侧的监控在报数据库连接失败业务同事说订单页面全部打不开而值班的同学远程登上数据库服务器敲了sqlplus发现实例还在、进程还在、监听也还在就是谁也连不进去。最后在 alert 日志里翻出一行ORA-00257: archiver error. Connect internal only, until freed.问题才算有了名字。如果你正在读这篇东西大概率是三种人之一第一种是凌晨被叫起来救火的 DBA需要马上止血第二种是写了几年 PHP 或者 Java 业务代码第一次看到这个报错想知道这玩意儿到底是不是我程序的锅第三种是把这套流程沉淀下来准备下次不再手忙脚乱的运维同学。这篇内容就是按止血—定位—根治—应用侧兜底的顺序来组织的所有命令我都按能直接复制粘贴的形式给出来参数和容量计算也会把推导过程写清楚方便你对着自己环境里的数字套一遍。先把结论放前面ORA-00257 从来不是一个等等就好的错误它不会自己恢复必须人工介入而且越晚处理影响面越大。1. 先把 ORA-00257 读透它到底在说什么1.1 错误文本里藏着三个关键信息完整的报错一般是这样的ORA-00257: archiver error. Connect internal only, until freed.很多人看到这句话第一反应是连不上数据库然后开始怀疑网络、监听、防火墙、连接池配置方向就跑偏了。实际上这三个短语各自承载了不同的信息量拆开看就清楚多了。archiver error告诉我们出错的主体是归档进程ARCn不是 SQL 解析器不是权限校验也不是网络层。归档进程干的事情很单一把在线重做日志整块复制成一个归档日志文件。它出错的常见原因就那么几类——目标路径写不进去、目标空间满了、目标路径权限不对。所以排查范围一下子从整个数据库收缩到了归档目的地。Connect internal only是 Oracle 的一种自我保护机制。归档区写不进去以后数据库无法保证崩溃恢复能力继续对外提供写服务就等于在裸奔所以它直接把普通会话的口子关掉了只留给 SYSDBA / SYSPOPER 这类内部连接一条缝让你登进去处理。这也解释了一个现象为什么应用是整片挂掉而不是部分接口报错——因为普通业务账号连会话都建立不了。until freed是最容易被忽略的一句也是最重要的定调。它的意思是在空间被释放出来之前这个状态不会改变。也就是说ORA-00257 是自愈不了的那一类错误跟 ORA-00054资源忙那种等几秒就过去了完全是两种性质。1.2 归档日志、闪回恢复区、控制文件之间的三方关系要讲清楚为什么会满得先把三个角色的关系理顺。数据库处于归档模式ARCHIVELOG时在线重做日志是循环使用的。假设你有三组重做日志日志组 1 写满后切换到日志组 2日志组 3然后再回到日志组 1。回到日志组 1 之前它里面的内容必须先被归档出来否则这段重做数据一旦被覆盖就没法做时间点恢复了。负责这件事的 ARCn 进程会把日志组的内容整块复制成一个文件默认命名类似1_4521_987654321.arc。这个文件写到哪里由LOG_ARCHIVE_DEST_1决定。绝大多数环境里这个参数的值是USE_DB_RECOVERY_FILE_DEST意思是写到我配置的那个闪回恢复区里。闪回恢复区Fast Recovery AreaFRA的物理位置由DB_RECOVERY_FILE_DEST指定容量上限由DB_RECOVERY_FILE_DEST_SIZE指定。同时控制文件里会记录每一条归档日志的路径、sequence、线程号、状态。这就带来一个很关键的副作用控制文件是账本磁盘上的文件是实物。账本和实物不一致的时候就会出现各种各样的怪问题后面第 6 章会专门讲。把这条链路串起来看归档写不进去 → 在线日志无法被覆盖 → 日志切换卡死 → 检查点checkpoint无法推进 → 数据库的写操作逐步挂起。也就是说如果放着不管影响会从应用连不上升级成数据库整体停摆。1.3 为什么应用侧只看到连不上数据库从应用的角度看这个错误的呈现方式往往带有欺骗性。已经建立的连接池连接短时间内可能还能用因为 TCP 层是好的、会话已经建立。但只要业务执行到需要提交事务、或者触发日志切换相关的操作就可能失败。更常见的是连接池需要扩容新连接oci_connect直接返回失败。用 PHP OCI8 的同学会拿到ORA-00257用 JDBC 的同学可能拿到ORA-12537TNS 连接关闭或者被连接池包装成获取连接超时还有些中间件会把原始错误吃掉统一报一个系统繁忙。这是最坑的地方——错误信息被层层包装之后真正的根因被埋掉了。所以我的习惯是不管上层怎么包装应用的错误日志里必须保留原始的 Oracle 错误码和错误文本否则每次排查都要多花半小时去确认到底是数据库的问题还是我网络的问题。2. 十分钟定位把根因锁死在具体文件类型上2.1 第一步永远是看恢复区余量处理这类问题顺序比命令更重要。第一件事不是删文件而是看清楚恢复区现在到底是什么状态。sqlplus / as sysdba SQL SET LINESIZE 220 SQL COL NAME FOR A42 SQL SELECT NAME, 2 ROUND(SPACE_LIMIT/1024/1024/1024,2) AS LIMIT_GB, 3 ROUND(SPACE_USED/1024/1024/1024,2) AS USED_GB, 4 ROUND(SPACE_RECLAIMABLE/1024/1024/1024,2) AS RECLAIM_GB, 5 ROUND(SPACE_USED/SPACE_LIMIT*100,2) AS USED_PCT, 6 NUMBER_OF_FILES 7 FROM V$RECOVERY_FILE_DEST;典型输出长这样NAME LIMIT_GB USED_GB RECLAIM_GB USED_PCT NUMBER_OF_FILES ---------------------------------------- --------- --------- ----------- --------- --------------- /u01/app/oracle/fast_recovery_area 100 100.00 91.37 100.00 1521这里有三个数字要重点看不是只看USED_PCT。LIMIT_GB是你给恢复区设的软上限。注意软这个字——它跟物理磁盘容量是两回事。有可能磁盘还剩 500G但你把上限设成了 100G那照样会满。USED_GB是当前已用。它满了只是说明到顶了但不代表没救。RECLAIM_GB才是决定你走哪条路的关键。它表示当前占用中有多少是可以被回收的——比如已经超过保留期、或者已经备份过的归档。上面这个例子里 91.37GB 可回收意味着你完全不需要扩容删一轮归档就回去了。反过来如果RECLAIM_GB很小比如 2GB那说明空间是真不够用删也删不出多少必须走扩容或者调整归档目的地这条路。提示判断删一删能不能解决和必须扩容的分界线就是SPACE_RECLAIMABLE这个字段。先看它再决定动作能省掉很多无用功。2.2 第二步区分是归档占满还是备份/闪回日志占满光知道满了还不够得知道是谁把它吃掉的因为针对不同文件类型的处理方式完全不一样。SQL COL FILE_TYPE FOR A22 SQL SELECT FILE_TYPE, 2 PERCENT_SPACE_USED, 3 PERCENT_SPACE_RECLAIMABLE, 4 NUMBER_OF_FILES 5 FROM V$RECOVERY_AREA_USAGE 6 ORDER BY PERCENT_SPACE_USED DESC;这个视图会把恢复区里的文件按用途分类列出常见的几类及含义如下FILE_TYPE含义满了以后的典型处理ARCHIVED LOG归档日志文件最最常见的元凶清理或转移目的地BACKUP PIECERMAN 备份片检查保留策略或把备份改写到独立盘/带库IMAGE COPY数据文件镜像副本说明有人做过BACKUP AS COPY且没清理FLASHBACK LOG闪回日志检查是否开了闪回或存在保证还原点FOREIGN ARCHIVED LOG从其他库传过来的归档常见于备库或跨库传输场景CONTROL FILE / REDO LOG控制文件与在线日志副本占用很小一般可以忽略对照着看基本一眼就能定位ARCHIVED LOG高、PERCENT_SPACE_RECLAIMABLE也高恭喜删归档就完事了。ARCHIVED LOG高、PERCENT_SPACE_RECLAIMABLE低要么保留窗口设得太长要么最近归档量暴涨大批量作业、异常日志切换需要动策略。FLASHBACK LOG高闪回数据库开着或者存在带GUARANTEE FLASHBACK DATABASE的还原点这种最隐蔽。BACKUP PIECE高备份片和归档在抢同一块空间架构上就该拆开了。2.3 第三步回看 alert 日志的时间线上面两个查询告诉你现在alert 日志告诉你什么时候开始的、怎么恶化的。cd $ORACLE_BASE/diag/rdbms/$(echo $ORACLE_SID | tr A-Z a-z)/$ORACLE_SID/trace grep -nE ORA-19815|ORA-19809|ORA-19804|ORA-16038|ORA-00257 \ alert_${ORACLE_SID}.log | tail -60我特别建议你按这个顺序看这几个错误码因为它们是一条清晰的恶化曲线ORA-19815: WARNING: db_recovery_file_dest_size of 107374182400 bytes is 97.31% used ... ORA-19815: WARNING: db_recovery_file_dest_size of 107374182400 bytes is 100.00% used, and has 0 remaining bytes available. ORA-19809: limit exceeded for recovery files ORA-19804: cannot reclaim 52428800 bytes disk space from 107374182400 limit ARCH: Archival stopped, error occurred. Will continue retrying ORA-16038: log 3 sequence# 4521 cannot be archived ORA-19809: limit exceeded for recovery files ORA-00312: online log 3 thread 1: /u01/app/oracle/oradata/ORCL/redo03.log ORA-00257: archiver error. Connect internal only, until freed.ORA-19815是预警从 90% 左右就开始刷这时候处理成本最低。ORA-16038是恶化信号——某个在线日志组归档失败意味着它不能被覆盖了。等到ORA-00257出现其实已经过了好几道防线只是前面没人看。所以我在自己环境里做监控的时候抓的第一个关键字不是ORA-00257而是ORA-19815。抢在前面动手整个故障根本不会发生。2.4 归档生成速率实测判断多大才算够的地基数据很多人扩容的时候是拍脑袋的满了 100G 就改成 200G下次再满再改 400G。这样做事倍功半。真正该做的是先量一下归档的生成速率。按天统计最近两周的归档量SQL SELECT TRUNC(COMPLETION_TIME) AS DAY, 2 COUNT(*) AS ARCH_FILES, 3 ROUND(SUM(BLOCKS*BLOCK_SIZE)/1024/1024/1024,2) AS ARCH_GB 4 FROM V$ARCHIVED_LOG 5 WHERE COMPLETION_TIME SYSDATE - 14 6 GROUP BY TRUNC(COMPLETION_TIME) 7 ORDER BY 1;再按小时看日志切换频率SQL SELECT TO_CHAR(FIRST_TIME,MM-DD HH24) AS HOUR, 2 COUNT(*) AS SWITCHES 3 FROM V$LOG_HISTORY 4 WHERE FIRST_TIME SYSDATE - 2 5 GROUP BY TO_CHAR(FIRST_TIME,MM-DD HH24) 6 ORDER BY 1;这两个查询出来以后容量规划就有据可依了。这里有一个特别容易搞错的概念值得单独说调整在线重做日志的大小并不会减少归档的总量。举个例子你现在重做日志每组 200MB每 5 分钟切换一次那么归档速率是200MB / 5min 40MB/min 40MB/min × 60 2400MB/h 2.4GB/h 2.4GB/h × 24 57.6GB/day如果我把重做日志改成 1GB 一组切换间隔变成 25 分钟速率变成1024MB / 25min 40.96MB/min ≈ 2.46GB/h ≈ 59GB/day几乎一样。因为归档总量本质上等于重做数据的生成速率而重做生成速率由业务写入量决定跟重做日志文件多大没关系。调整重做日志大小的真正收益是减少切换次数、减少 checkpoint 频率、减少归档文件个数、降低 ARCn 的调度压力。它不会帮你省空间。想真正降低归档量得从这几个方向入手大批量导入导出和数据维护操作尽量用 NOLOGGING 或者直接路径加载减少不必要的索引维护和全表更新把日报、月结这类集中写入作业错峰避免短时间内重做暴增。2.5 常见触发场景对照表把上面几步的信息拼起来基本就能对号入座了场景典型特征处理方向归档保留窗口过长RECLAIM低归档文件序列号跨度大缩短保留策略或加删除策略突发的批量作业某一天归档 GB 数明显高于均值 3 倍以上排查作业、错峰、改用 NOLOGGING恢复区上限设小了磁盘实际还剩很多LIMIT却很小直接扩容上限备份片堆积BACKUP PIECE占比高调整保留策略或把备份写到独立盘保证还原点未清理FLASHBACK LOG占比持续增长删还原点或关闭闪回备库延迟归档删不掉报 RMAN-08137先修备库链路再谈清理归档目的地权限异常每次都报 ORA-19502/ORA-16014检查目录属主与权限3. 应急止血让业务几分钟内恢复连接3.1 最优先动作先扩容再清理故障现场的第一原则是先让库活过来再慢慢分析。扩容是一条 SQL 的事SQL ALTER SYSTEM SET DB_RECOVERY_FILE_DEST_SIZE 300G SCOPEBOTH;为什么把它放在第一位因为这是一个元数据层面的操作不涉及实际的磁盘写入分配执行是秒级的立刻就能解除写不进去的限制把数据库从悬崖边拉回来。业务能连上了你才有从容的时间做后面的清理。但它有两个前提条件必须先在操作系统层面确认df -h /u01/app/oracle/fast_recovery_area第一新设的上限不能超过底层文件系统实际可用空间。设一个 300G 的上限但盘上只剩 50G那是自欺欺人过一会儿照样满而且这次会连带影响操作系统层面的其他进程。第二如果是 RAC 环境注意作用域-- 所有实例一起改 ALTER SYSTEM SET DB_RECOVERY_FILE_DEST_SIZE 300G SCOPEBOTH SID*;注意扩容只是一个缓冲垫不是解决方案。如果归档生成速率是 60G/天你把上限从 100G 提到 300G也就是多撑三四天而已。扩完必须接着做清理和策略调整。3.2 RMAN 交叉校验与过期归档清理扩容之后紧接着就是清理。这一步用 RMAN不要用操作系统的rm。rman target /RMAN CROSSCHECK ARCHIVELOG ALL; RMAN DELETE EXPIRED ARCHIVELOG ALL; RMAN DELETE NOPROMPT ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-3; RMAN DELETE NOPROMPT OBSOLETE;这四条命令的顺序是有讲究的不能乱。CROSSCHECK ARCHIVELOG ALL做的事情是拿控制文件里的记录去磁盘上逐个比对。文件在标记为 AVAILABLE文件不在标记为 EXPIRED。为什么这一步必须放在最前面因为实际运维中有人手工删过归档文件这件事发生的概率远比想象中高。一旦控制文件里记录了某个文件、磁盘上却找不到后面的 DELETE 会直接报错中断清理流程就走不下去。DELETE EXPIRED ARCHIVELOG ALL把刚刚标记为 EXPIRED 的记录从控制文件里清掉让账本和实物对上。DELETE ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-3是真正的删除动作删掉三天前完成的所有归档同时从磁盘和控制文件里一起移除。DELETE NOPROMPT OBSOLETE交给 RMAN 按保留策略自己判断哪些过期了这是日常最省心的一条。删的过程中如果想知道还有多少没删、哪些是因为什么原因删不掉RMAN LIST ARCHIVELOG ALL; RMAN REPORT OBSOLETE;有时候会碰到删不掉的情况RMAN 会给你一句RMAN-08137: WARNING: archived log not deleted, needed for standby or upstream capture process。这个后面 6.2 会专门讲。还有一个命令要提一下但强烈建议慎用RMAN DELETE FORCE ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-1;FORCE的意思是不管有没有备份、不管备库需不需要都给我删。它是核武器能解决眼前的燃眉之急但代价是那段窗口期失去了时间点恢复能力。我的态度是除非数据库已经彻底写不进去、业务停摆、而且你确认过备份是完好的否则不要用。用之前一定要记录下当前的最早可用 SCN。3.3 把归档迁到独立目录或独立磁盘如果这已经是今年第三次遇到那说明方案本身有问题——归档和备份片挤在同一个恢复区里两个都容易膨胀的东西互相抢空间迟早要出事。这时候可以考虑把归档单独挪出去。SQL ALTER SYSTEM SET LOG_ARCHIVE_DEST_1LOCATION/arch/orcl SCOPEBOTH;动手之前先在操作系统层面把目录建好、权限给对mkdir -p /arch/orcl chown oracle:oinstall /arch/orcl chmod 750 /arch/orcl改完之后主动触发一次日志切换来验证SQL ALTER SYSTEM SWITCH LOGFILE;然后去新目录里看有没有生成.arc文件同时确认归档目的地状态正常SQL SELECT DEST_ID, STATUS, DESTINATION, ERROR 2 FROM V$ARCHIVE_DEST_STATUS 3 WHERE DEST_ID 1;STATUS应该是VALIDERROR应该是空的。这么改的好处很直接恢复区里只剩备份片和闪回日志容量压力小一大截归档盘可以单独挂一块大盘扩容的时候互不影响。注意改归档目的地属于生产变更改之前务必确认新路径的可用空间和权限。如果新路径不可写数据库会立刻抛出 ORA-16014 / ORA-19502比原来的问题更麻烦——归档连地方都写不了。变更前建议先在测试环境走一遍或者至少准备好回滚命令。3.4 应急阶段的四个禁忌救火的时候越着急越容易做错事。这几条是我见过最多的翻车点。第一不要在操作系统层面直接rm归档文件。这是最典型的错误。文件是没了但控制文件完全不知情V$RECOVERY_FILE_DEST的 USED 也不会马上反映。后续做增量备份、做恢复、做 Data Guard 同步的时候会报出一堆莫名其妙的错误。要删就通过 RMAN 删。第二不要在没确认备库应用进度之前把归档删干净。有备库的环境里主库的归档在备库应用完成之前是有用的。删早了备库就断了重建备库的成本远高于加一块硬盘。第三不要直接把DB_RECOVERY_FILE_DEST改成一个新目录来换地方。这不是换个路径那么简单Oracle 在新位置会重建目录结构旧内容需要另外处理而且这个参数在实例运行期间通常不建议随意改动。想换地方改LOG_ARCHIVE_DEST_1更安全。第四不要在恢复区接近满了的时候去跑大备份。备份片写不进恢复区也会失败报 ORA-19809而且失败前的写入还会进一步挤压剩余空间。3.5 验证闭环怎么确认真的好了清理完不能拍拍屁股就走得有验证动作。我一般固定做四件事。先看恢复区水位降下来没有SQL SELECT ROUND(SPACE_USED/SPACE_LIMIT*100,2) AS USED_PCT, 2 ROUND(SPACE_RECLAIMABLE/1024/1024/1024,2) AS RECLAIM_GB 3 FROM V$RECOVERY_FILE_DEST;再主动切一次日志然后立刻去看 alert 日志尾部有没有新报错SQL ALTER SYSTEM SWITCH LOGFILE;tail -n 50 $ORACLE_BASE/diag/rdbms/*/${ORACLE_SID}/trace/alert_${ORACLE_SID}.log然后用一个普通业务账号不是 SYSDBA试着连一次这一步很关键因为只有普通账号能连上才说明 ORA-00257 的状态真正解除了。最后确认归档进程状态SQL SELECT PROCESS, STATUS, THREAD#, SEQUENCE#, BLOCK# 2 FROM V$ARCHIVE_PROCESSES;STATUS 应该是IDLE或者BUSY如果是STOPPED或者ERROR说明还有问题没解决。4. 长期治理容量规划与保留策略4.1 DB_RECOVERY_FILE_DEST_SIZE 该怎么算扩容不能拍脑袋按下面这个公式走所需恢复区容量 ≈ 每日归档量 × 需要保留的天数 × 安全系数 1.5 备份片峰值占用 闪回日志预留拿前面 2.4 节测出来的数据套一遍每日归档 57.6GB保留 7 天安全系数 1.557.6 × 7 403.2 GB 403.2 × 1.5 ≈ 605 GB也就是说光归档就需要 600GB 左右的空间。如果备份片还放在同一个恢复区里每天一份全备 300GB、保留两份那就再加 600GB总共 1.2TB。到这一步你应该会发现一个不对劲的地方这个数字大得离谱。这时候正确的做法不是去申请一块 1.2TB 的盘而是反思架构——备份片根本不该和归档挤在同一个恢复区。备份应该直接写到独立磁盘、磁带库或者对象存储上。恢复区里只保留归档和必要的控制文件自动备份容量需求会立刻下降到可控范围。安全系数为什么取 1.5因为归档量是波动的。月末结账、季度报表、批量数据同步这些场景当天的归档量可能是均值的两三倍。留 50% 的余量是为了让这些峰值不至于直接顶到上限。保留天数怎么定这取决于你的备份周期。如果是每日全备归档保留 2 到 3 天通常就足够恢复用了。如果是每周全备加每日增量那归档至少要覆盖上一个完整全备到当前的整个区间比如 8 天。这里的原则是保留期内必须能够完成一次完整的恢复到任意时间点多一天都是浪费空间。4.2 保留策略与归档删除策略的搭配前面DELETE OBSOLETE能不能正确判断哪些该删完全取决于 RMAN 的策略配置。rman target /RMAN CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 7 DAYS; RMAN CONFIGURE ARCHIVELOG DELETION POLICY TO BACKED UP 1 TIMES TO DEVICE TYPE DISK;第一条是保留策略保证最近 7 天内的任意时间点都能恢复。RMAN 会据此判断哪些备份和归档算过期。第二条是归档删除策略专门管归档规则是至少被备份过一次才允许从磁盘删除。这条件的意义在于避免归档还没备份就被删了结果需要恢复时找不到。如果备份是直接写到磁带库的可以配成RMAN CONFIGURE ARCHIVELOG DELETION POLICY TO BACKED UP 2 TIMES TO DEVICE TYPE SBT;意思是归档必须成功备份到磁带两份才允许删本地。这个数字看你的容灾要求两份是为了防止单份介质损坏。有备库的环境还有一条RMAN CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY;这条的意思是归档必须在所有备库上都应用完成了才允许删除。这是有备库场景下最安全的配置但也是最容易导致主库归档堆积的配置——因为只要有一个备库卡住主库的归档就一份都删不掉。提示保留策略和归档删除策略同时配置时RMAN 会取两者中更严格的那个来执行。所以不要把保留窗口设成 30 天、同时又期望归档能被快速清掉这两个目标是互相冲突的。4.3 闪回日志这个隐形大户这是我认为最值得单独讲的一类原因因为它最隐蔽——归档删得干干净净容量却还在涨。先看几个查询SQL SHOW PARAMETER DB_FLASHBACK_RETENTION_TARGET; SQL SELECT * FROM V$FLASHBACK_DATABASE_LOG; SQL SELECT NAME, SCN, TIME, GUARANTEE_FLASHBACK_DATABASE, STORAGE_SIZE 2 FROM V$RESTORE_POINT;DB_FLASHBACK_RETENTION_TARGET的单位是分钟1440 就代表希望保留 1 天的闪回能力。这个参数本身不强制占用空间它只是一个目标闪回日志会根据实际写入情况动态调整。真正会导致空间被吃光的是V$RESTORE_POINT里GUARANTEE_FLASHBACK_DATABASE YES的那种保证还原点。一旦建了这种还原点Oracle 就不能删除还原点之前产生的任何闪回日志因为删了就达不到保证能闪回到那个点的承诺。结果就是闪回日志只增不减慢慢把恢复区填满最后报 ORA-00257。我遇到过的一个典型案例某次大版本升级前有人为了万一升级失败能闪回去建了个保证还原点。升级顺利完成但还原点没人清理两个月后恢复区满了报 ORA-00257排查了半天才想到去查V$RESTORE_POINT。处理方式很直接-- 确认不再需要后删除还原点 SQL DROP RESTORE POINT BEFORE_UPGRADE_20240315;如果确认环境根本不需要闪回能力SQL ALTER DATABASE FLASHBACK OFF;关掉之前一定要确认没有正在进行的闪回相关操作并且业务上确实不需要这个能力。4.4 把清理和监控做成定时任务靠人记得每天手工清一定会忘。这两件事必须自动化。先写一个 RMAN 清理脚本比如放在/home/oracle/scripts/purge_arch.rmanCROSSCHECK ARCHIVELOG ALL; DELETE EXPIRED ARCHIVELOG ALL; DELETE NOPROMPT ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-3; DELETE NOPROMPT OBSOLETE; EXIT;再写一个调用脚本/home/oracle/scripts/purge_arch.sh#!/bin/bash export ORACLE_SIDorcl export ORACLE_HOME/u01/app/oracle/product/19.0.0/dbhome_1 export PATH$ORACLE_HOME/bin:$PATH export NLS_DATE_FORMATYYYY-MM-DD HH24:MI:SS mkdir -p /home/oracle/logs rman target / cmdfile/home/oracle/scripts/purge_arch.rman \ log/home/oracle/logs/purge_arch_$(date %Y%m%d).log然后挂到 cron 上每天凌晨三点跑0 3 * * * /home/oracle/scripts/purge_arch.sh监控脚本同样简单抓一个百分比出来就能用#!/bin/bash export ORACLE_SIDorcl export ORACLE_HOME/u01/app/oracle/product/19.0.0/dbhome_1 export PATH$ORACLE_HOME/bin:$PATH PCT$(sqlplus -s / as sysdba EOF set pages 0 lines 200 feed off head off trimspool on select round(space_used/space_limit*100,2) from v$recovery_file_dest; EOF ) echo FRA used: ${PCT}% if [ $(echo $PCT 90 | bc) -eq 1 ]; then echo CRITICAL: FRA usage ${PCT}% exit 2 elif [ $(echo $PCT 80 | bc) -eq 1 ]; then echo WARNING: FRA usage ${PCT}% exit 1 fi exit 0告警阈值我建议这样分档别等 100% 才报警使用率状态建议动作 70%正常无需干预70% - 85%关注检查清理任务是否正常执行85% - 95%预警人工介入清理评估是否需要扩容 95%紧急立即处理准备扩容方案4.5 备库与双活环境下的额外约束有 Data Guard 或者类似主备架构的环境归档管理会多一层约束因为主库的归档在备库应用完成前不能删。查看备库的接收和应用进度SQL SELECT DEST_ID, STATUS, RECOVERY_MODE, APPLIED_SEQ, 2 ERROR 3 FROM V$ARCHIVE_DEST_STATUS 4 WHERE DEST_ID 2;APPLIED_SEQ是备库已经应用到的最大序列号。跟主库当前的日志序列号一对比就能算出延迟了多少个日志SQL SELECT THREAD#, SEQUENCE# FROM V$LOG WHERE STATUS CURRENT;如果发现ARCHIVED_SEQ - APPLIED_SEQ在持续扩大说明备库跟不上或者链路有问题主库的归档会一直堆积怎么清都清不掉。这种情况下扩容只是拖延时间真正要修的是同步链路本身——可能是带宽不够、可能是备库的磁盘 I/O 跟不上、也可能是某个批次应用卡住了。我在这种场景里的做法是先在主库确认延迟趋势是稳定还是持续扩大再上备库看归档应用速率和等待事件。如果延迟只是暂时的比如主库刚跑完一个大批量作业那就临时扩大恢复区扛过去如果是持续扩大那清理动作完全没有意义必须先把链路问题解决。5. 应用侧怎么办从报错到可用的错误处理写法5.1 为什么 PHP 层不能简单地重试到成功用 PHP OCI8 的同学看到 ORA-00257第一反应往往是加个重试循环超时就重连。这个思路在应对网络抖动时是对的但对付 ORA-00257 是会出大问题的。原因在于ORA-00257 是实例级故障不是单个连接的偶发问题。当你加上重试 N 次每次等几秒的逻辑而高峰期可能有几百个 PHP-FPM 工作进程同时在跑的时候会发生这么一件事——所有进程都在sleep进程池被占满新的请求排不上队前端从部分接口报错变成整站 502。原本只是数据库的故障被应用层放大成了全站故障。正确的姿势是三层短重试1 到 2 次带随机抖动 快速失败 熔断。重试只是为了应对极短时间的瞬态熔断是为了防止雪崩。5.2 一个带退避与熔断的连接封装下面这个类是我在实际项目里用的简化版本思路是从报错就重试改成识别到 257 就立刻熔断并快速失败。?php final class OraConnection { private const CACHE_KEY ora257_circuit; private const OPEN_SECONDS 15; // 熔断持续时间 private const MAX_ATTEMPTS 2; public static function connect(string $user, string $pass, string $dsn) { // 熔断器打开状态直接快速失败不再尝试连接数据库 if (function_exists(apcu_fetch) apcu_fetch(self::CACHE_KEY)) { throw new RuntimeException(数据库繁忙请稍后重试, 257); } for ($i 1; $i self::MAX_ATTEMPTS; $i) { $conn oci_connect($user, $pass, $dsn, AL32UTF8); if ($conn ! false) { return $conn; } $err oci_error(); $code isset($err[code]) ? (int) $err[code] : 0; $message isset($err[message]) ? $err[message] : ; $isArchiverError ($code 257) || (stripos($message, ORA-00257) ! false); if ($isArchiverError) { if (function_exists(apcu_store)) { apcu_store(self::CACHE_KEY, 1, self::OPEN_SECONDS); } error_log(sprintf( [ORA] ORA-00257 归档区写满dsn%s已触发熔断 %d 秒, $dsn, self::OPEN_SECONDS )); throw new RuntimeException(数据库繁忙请稍后重试, 257); } if ($i self::MAX_ATTEMPTS) { error_log(sprintf([ORA] 连接失败code%dmessage%s, $code, $message)); throw new RuntimeException(数据库暂时不可用, $code); } // 随机抖动避免所有进程同一时刻集中重试 usleep(random_int(80, 200) * 1000); } throw new RuntimeException(数据库暂时不可用, 0); } }几个细节值得说明。code 257和字符串匹配同时判断是因为不同版本的 OCI 驱动在连接失败时返回的错误码行为不完全一致有的场景下code取不到 257但message里一定包含ORA-00257这个文本。双保险成本几乎为零。熔断窗口设成 15 秒是一个经验值。太短了没意义数据库不会那么快好太长了会导致数据库已经恢复了你还在拒绝流量。15 秒意味着最多每 15 秒有一个请求去试探一次其它请求全部快速失败不会打满进程池。随机抖动80 到 200 毫秒是为了躲开惊群——如果所有请求都固定等 1 秒重试它们会在同一时刻全部冲回数据库等于制造了一次小型压测。5.3 更重要的让运维比用户先知道应用侧的错误捕获最大的价值不是让页面好看一点而是给运维争取时间。在捕获到 257 的地方除了error_log一定要往监控系统打一个独立的事件标记带上实例名和 DSN。这样运维的告警通道能在用户投诉之前就响起来。很多团队的问题是监控只看接口成功率和响应时间而这两个指标在数据库刚满的时候可能还没明显变化——因为连接池里还有存量连接撑着。等到成功率真的掉下去已经晚了二十分钟。配合第 4.4 节数据库侧的 80% 阈值告警就形成了双保险数据库侧提前预警应用侧兜底确认。6. 常见问题与排查技巧实录6.1 删了文件空间却不释放这是问得最多的一个问题明明用rm删了几十个 G 的归档为什么V$RECOVERY_FILE_DEST的USED_GB一点没变原因在于控制文件是账本rm只动了实物没动账本。Oracle 依然认为那些文件存在依然把它们算在占用里。正确的处理顺序rman target /RMAN CROSSCHECK ARCHIVELOG ALL; RMAN DELETE EXPIRED ARCHIVELOG ALL;CROSSCHECK负责把账上有、实物没了的记录标记为 EXPIREDDELETE EXPIRED负责把这些记录从账本上划掉。两步做完再查V$RECOVERY_FILE_DEST数字就对了。如果做完这两步数字还是不对按下面的顺序继续查是不是归档本身被备库或者闪回需要RMAN 拒绝删除看 RMAN 的输出里有没有RMAN-08137。是不是删除的只是恢复区里的一部分类型查V$RECOVERY_AREA_USAGE看看是不是BACKUP PIECE或者FLASHBACK LOG占着大头。是不是监控查的是缓存值V$RECOVERY_FILE_DEST的数据一般会很快刷新但如果是在同一个会话里反复查可以新开一个会话确认。6.2 RMAN-08137归档明明删了却说不能用完整报错大概是这个形式RMAN-08137: WARNING: archived log not deleted, needed for standby or upstream capture process archived log file name/u01/app/oracle/fast_recovery_area/ORCL/archivelog/2024_03_15/o1_mf_1_4521_abc123.arc thread1 sequence4521从字面意思就能看出来RMAN 认为这条归档还被需要所以拒绝删。有三种可能的原因处理方式完全不同。第一种配置了ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY而备库确实还没应用到这个序列号。这是最正常的情况也说明你的策略配得是安全的。这时候不应该想着怎么绕过它而应该去看备库为什么落后SQL SELECT DEST_ID, STATUS, RECOVERY_MODE, APPLIED_SEQ 2 FROM V$ARCHIVE_DEST_STATUS WHERE DEST_ID 2;第二种备库已经不用了但主库的LOG_ARCHIVE_DEST_2还配着状态是ERROR或者DEFERRED。这种情况下归档永远等不到被应用完成自然永远删不掉。处理方式是把失效的目的地清掉SQL ALTER SYSTEM SET LOG_ARCHIVE_DEST_STATE_2 DEFER; SQL ALTER SYSTEM SET LOG_ARCHIVE_DEST_2 ;第三种环境里有下游的日志捕获进程比如做数据同步用的它在等这条归档。这种情况要先去处理那个进程。如果确认以上三种都不适用而且情况紧急可以临时用RMAN DELETE FORCE ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-2;用之前务必记下当前的最早可恢复 SCNSQL SELECT MIN(SEQUENCE#) FROM V$ARCHIVED_LOG WHERE STATUS A;6.3 ORA-19809 / ORA-19804备份也一起挂了恢复区满了以后不只是归档写不进去RMAN 备份同样会失败ORA-19809: limit exceeded for recovery files ORA-19804: cannot reclaim 52428800 bytes disk space from 107374182400 limitORA-19809说的是恢复区超限了ORA-19804说的是想回收 50MB 空间但回收不出来。后者的出现说明 Oracle 已经尝试过自动清理最老的归档但清理不掉——通常是因为那些归档还在保留期内或者还被备库需要。处理顺序还是老一套先扩容上限解压再清理。但这里有个额外的注意点如果备份是写到恢复区的最好把备份输出改成独立路径别让备份和归档继续抢空间。RMAN BACKUP DATABASE PLUS ARCHIVELOG FORMAT /backup/rman/%U;指定明确的FORMAT路径把备份写到独立的备份盘上。6.4 归档进程卡住与 ARCn 状态异常有时候空间明明够了归档还是不出。这时候要看归档进程本身的状态SQL SELECT PROCESS, STATUS, THREAD#, SEQUENCE#, BLOCK# 2 FROM V$ARCHIVE_PROCESSES; SQL SELECT GROUP#, SEQUENCE#, STATUS, ARCHIVED, MEMBERS 2 FROM V$LOG;正常情况下V$ARCHIVE_PROCESSES.STATUS应该是IDLE空闲待命或者BUSY正在归档。如果出现STOPPED说明归档进程已经停了。V$LOG里ARCHIVED列如果是NO说明这个日志组还没归档成功也就不能被覆盖。常见的原因有两个。一个是归档目的地权限或者空间问题这个在 alert 日志里会有明确的 ORA-19502 之类的报错。另一个是LOG_ARCHIVE_MAX_PROCESSES设得太小高并发写入时归档跟不上日志切换的速度。默认是 4如果日志切换非常频繁可以适当调大SQL SHOW PARAMETER LOG_ARCHIVE_MAX_PROCESSES; SQL ALTER SYSTEM SET LOG_ARCHIVE_MAX_PROCESSES 8 SCOPEBOTH;但要注意加归档进程只能提高归档的吞吐如果瓶颈在磁盘写入速度上加进程反而会加剧 I/O 竞争。所以调这个参数之前最好先看看那块盘的实际写入性能。6.5 问题速查表把上面这些整理成一张表应急的时候可以按症状直接查。现象最可能的原因第一步动作应用全部连不上只有 SYSDBA 能连恢复区满查V$RECOVERY_FILE_DEST扩容USED_PCT100% 但RECLAIM_GB很大归档过期未清理RMAN crosscheck delete obsoleteUSED_PCT100% 且RECLAIM_GB很小容量真不足或保留期太长扩容 调整保留策略删了文件空间不降控制文件账本未更新CROSSCHECKDELETE EXPIRED报 RMAN-08137归档被备库或闪回需要查V$ARCHIVE_DEST_STATUS只有FLASHBACK LOG在涨保证还原点未清理查V$RESTORE_POINTARCn状态 STOPPED目的地不可写或进程数不足看 alert 日志 调LOG_ARCHIVE_MAX_PROCESSES备份也失败报 ORA-19809备份片和归档抢空间把备份输出改到独立路径归档量突然翻几倍批量作业集中写入排查作业 考虑 NOLOGGING7. 我在几次真实故障里总结的几条经验7.1 我踩过的三个真实的坑第一个坑有一年我接手一个新环境看到恢复区里有大量归档第一反应是手工rm掉了一批释放空间。当时确实立刻见效了应用连上了。但三个月后做恢复演练的时候RMAN 报了一堆RMAN-06059找不到归档日志整个恢复流程走不下去最后不得不重新做一次全备才恢复演练能力。那次我记住了一件事归档的删除权只交给 RMAN手工删能救急但一定要在事后补一次 crosscheck。第二个坑有个环境每次都是BACKUP PIECE占满了恢复区我一直以为是保留策略太宽松。查了半天发现是有人写了个备份脚本每天跑一次全备但备份输出路径默认指向恢复区而保留策略里没配删除。结果就是每天加 300G一周就满了。这个问题的根因不在归档在备份架构。第三个坑某次升级之前建了保证还原点升级完成以后没人清理。两个月后 ORA-00257 出现我按常规套路删归档、扩容量折腾了两个小时都没解决——因为删掉的那点空间立刻被闪回日志补回去了。最后是查V$RESTORE_POINT才找到真凶。从此我的巡检清单里多了一项检查保证还原点是否存在。7.2 几条不太写在文档里的建议第一条把ORA-19815当成主告警而不是ORA-00257。前者是预警后者是已经出事了。这两个错误码之间的时间差通常有十几分钟到几个小时不等这段时间就是你从容处理的机会窗口。第二条不要在恢复区接近满的时候去做一些顺手的事比如临时跑个全备、导出一份数据。这些操作会进一步挤压空间把还能抢救变成只能扩容。第三条扩容这个动作本身没有成本所以不要舍不得。但扩容之后一定要记录什么时候扩的、当时是什么原因、扩到了多少。如果不记录下一次遇到同样的问题你会重新走一遍完整的排查流程。我自己的习惯是在变更记录里写清楚三项——触发原因、扩容前后的上限、以及是否调整了保留策略这三项决定了下次还会不会重复遇到。第四条也是我觉得最重要的一条ORA-00257 的根因排查最终一定会落到是容量问题还是策略问题。容量问题用钱解决策略问题用配置解决而搞错方向的代价是——你以为自己解决了实际上只是把下一次故障推迟了几天。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门