
我入行第八年的时候遇上一个特别棘手的生产事故。系统白天一切正常一到晚上八点就开始卡应用不停报超时。AWR报告拉出来看了好几轮TOP SQL没有明显异常等待事件也不集中。后来是一个老前辈提了一句你去翻翻归档日志的切换频率按小时统计一下。我这才发现问题根本不在我看的那几个时段而是在归档日志记录里业务真正的压力曲线和我预想的完全不同。从此之后“归档日志”就成了我看系统的第三只眼尤其是在定位业务高峰期的SQL问题上它比很多监控工具都来得可靠。这篇内容就是围绕这个思路展开的归档日志为什么能反映业务高峰、怎么从切换频率还原系统真实脉搏、又如何结合归档信息倒推高峰期的高消耗SQL。内容偏实战Oracle DBA、偏运维的开发者都适用不需要额外的商业插件纯靠数据库本身的视图和日志就能落地。1. 为什么需要“第三只眼”常规监控的盲区到底在哪先聊一个很实际的问题。很多系统不是没有监控而是监控的维度太“正”了。所谓正就是盯着CPU、内存、IOPS、活动会话数这些常规指标配合AWR、ASH去分析。这套组合拳在大多数场景下是有效的但有几个先天盲区。1.1 AWR和ASH的采样窗口决定了它们会“漏掉”瞬时高峰AWR默认是每60分钟采一次快照ASH虽然细到秒级但它依赖的是内存中的采样一旦会话结束、采样被覆盖历史信息就丢了。如果你面对的是一种“脉冲式”的业务高峰比如每小时的15分和45分各涌来一波批量任务每波只有两三分钟AWR的小时级快照大概率会把这种波动磨平。你看到的平均负载可能只有20%但实际高峰时刻的负载已经到了90%。更尴尬的是很多系统根本没有购买完整诊断包授权。在只使用基础版的环境里部分AWR和ASH功能是受限的。这时候你想回溯过去某一天到底跑了什么SQL抓手其实很少。而归档日志是数据库运行的自然产物只要数据库开着归档模式它就在持续产生它不依赖任何诊断包也不占额外的采样内存。1.2 应用层监控和SQL监控各有各的“失真”有人会说我们有应用链路追踪能看到接口调用量和耗时。这话对了一半。应用层监控记录的是“请求”和“响应”但它不知道一次请求在数据库内部产生了多少redo量。一个查询接口可能调用量巨大但实际上没多少redo一个不起眼的批处理存储过程调用频率极低却可能产生了整个库80%的归档日志。反过来纯SQL监控的问题在于它能告诉你“哪条SQL跑得久”但不容易告诉你“哪条SQL是推动系统压力上升的根源”。比如一条UPDATE语句执行计划走的是索引单次执行也就几毫秒但它在循环里被执行了几十万次累计产生的redo和锁等待才是系统卡顿的真凶。这种SQL靠执行计划分析发现不了但它在归档日志的量级里会暴露得很彻底。1.3 归档日志作为“黑盒记录仪”的独特价值归档日志本质上是一个持续追加的、带时间戳的物理变更流。数据库的每一次数据块变更只要commit了最终都会以redo record的形式落到归档日志里。这意味着几件事只要发生了真实的业务数据变更就一定会产生归档它是一个无法伪造的行为记录。归档日志的数量、大小、切换频率和业务系统的写入压力呈强相关天然就是业务活跃度的“心电图”。通过分析某个时间窗口内归档日志的增量或者切换次数可以反推出该时段数据库的写入压力等级再结合其它视图定位到具体SQL。这就是我把它叫作“第三只眼”的原因——它不替代AWR不替代应用监控但能在那些常规手段失灵、失真、缺数据的时候给你一个相对干净、可信的观察角度。2. 归档日志能告诉我们什么redo、日志切换量与业务高峰的原理要拿归档日志当分析工具先得弄明白它的生成链路。这一节把原理讲透后面操作才顺手。2.1 一条DML语句是怎么变成归档日志的Oracle的数据变更流程可以压缩成一句话会话执行DML数据块的变更以redo record的形式写入redo log bufferLGWR进程在合适的时机把redo buffer刷入在线重做日志文件online redo log在线日志写满后发生日志切换log switchARCn进程把写满的在线日志复制为归档日志archived redo log。这里面有两个容易被忽略的关键点。第一不是所有SQL都会产生redo。纯查询SELECT不会产生任何redo除非它涉及临时表空间的排序和哈希操作。DML和DDL才会产生redo而且DDL产生的redo往往比想象中更大比如CREATE INDEX重建、ALTER TABLE MOVE等操作产生的redo量是以GB计算的。这一点在后面对比业务高峰时要特别留意一个无业务写入的大表重建操作可能在归档日志上伪装成一次“业务高峰”。第二日志切换的颗粒度是可控的观察标尺。在线日志组的大小和数量决定了日志切换的频率。假设每个日志组是1GB你的系统每小时切换6次那每小时归档产生量大约是6GB。如果某一天某一小时的切换次数突然翻倍变成12次那这一小时内的写入压力大概率是平日的两倍。这个数字不需要额外的采集工具v$log_history里全都有。2.2 日志切换频率为什么比单纯的redo size更“皮实”你可能会问直接查v$sysstat里的redo size不是更精确吗这个思路没错但有个现实问题redo size统计的是实例启动以来的累计值如果你没有提前以小时或天为单位做差值采样事后很难还原某一小时的具体增量。日志切换次数则不同v$log_history记录的是每一次切换的第一时间天生自带时间戳事后回溯非常方便。另外redo size反映的是“绝对写入量”但日志切换次数反映的是“日志填满的速度”后者和压力波动的相关性更直观。举个例子一个库在线日志是4个组各2GB平时每小时才切3次说明写入量很小突然某天每小时切了20次哪怕每条SQL单看都不算慢你也能立刻意识到系统此刻处于异常写入状态。这个信号在监控图上特别醒目几乎不需要做任何数学处理。2.3 归档日志的时间戳精度与粒度边界归档日志能定位到秒级吗理论上可以。v$log_history里的FIRST_TIME精确到秒你可以知道这一秒发生了日志切换。但这个秒级精度在定位SQL上并没有太大意义因为一次日志切换背后的SQL可能成千上万条你无法直接把一条SQL和某个redo record精确对应起来。所以我的经验是归档日志更适合做“宏观定位”用它确定哪一天、哪个小时甚至哪一刻钟业务写入压力最大锁定一个相对狭窄的时间窗口然后在这个窗口里用其它手段去捕捉具体SQL。这种“大炮打坐标狙击枪打目标”的思路比纯靠AWR大海捞针高效得多。3. 日志切换频率监控从时区文件数字看业务脉搏原理讲完接下来上实操。第一步不是直接找SQL而是先把业务高峰的时间画像画出来。这一步用到的核心是v$log_history这个视图。3.1 用一条SQL复现“业务心电图”v$log_history在Oracle 10g以后的版本里都有记录了日志切换的历史。下面的SQL按小时汇总一天的日志切换次数基本就是数据库写入压力的“心电图”SELECT TO_CHAR(first_time, YYYY-MM-DD) AS day, TO_CHAR(first_time, HH24) AS hour, COUNT(*) AS log_switch_cnt FROM v$log_history WHERE first_time TO_DATE(2025-01-06 00:00:00, YYYY-MM-DD HH24:MI:SS) AND first_time TO_DATE(2025-01-07 00:00:00, YYYY-MM-DD HH24:MI:SS) GROUP BY TO_CHAR(first_time, YYYY-MM-DD), TO_CHAR(first_time, HH24) ORDER BY day, hour;输出大致长这样DAYHOURLOG_SWITCH_CNT2025-01-060022025-01-06011.........2025-01-061062025-01-0614182025-01-06203看到14时的切换次数明显异常18次基本可以断定下午两点有一个明显的写入高峰。这里有个细节切换次数只能说明“日志填满的速度”不能直接说明“单次写入的大小”。如果日志组大小统一切换次数和写入量是线性相关的如果日志组大小不一则要结合v$logfile里的日志文件大小换算成总体写入量。3.2 建立基线没有基线的数字都是噪音光看一天的曲线很难说明问题。任何系统都有自己的业务节奏比如日终批处理天天晚上十点跑归档切换次数天天晚上高于白天这是正常现象不是异常。关键在于“偏离基线”。我的做法是建一张历史基线表把近一个月的按小时切换次数统计出来计算平均值和标准差CREATE TABLE arch_switch_base AS SELECT TO_CHAR(first_time, YYYY-MM-DD) AS day, TO_CHAR(first_time, HH24) AS hour, COUNT(*) AS log_switch_cnt FROM v$log_history WHERE first_time SYSDATE - 30 GROUP BY TO_CHAR(first_time, YYYY-MM-DD), TO_CHAR(first_time, HH24);之后每次排查问题优先对比当天某小时的切换次数和过去30天同小时的平均值。如果超过均值2倍以上基本可以认定这个时段有异常写入如果在均值上下波动哪怕绝对数值很大大概率也只是常规业务节奏。这个“偏离度”思维比单纯看绝对数字可靠得多。3.3 用归档速度评估历史某天的“压力总量”除了v$log_historyv$archived_log也值得关注。它记录了实际生成的归档文件包括大小。需要回顾某一天的总写入压力时直接对v$archived_log按天做SUMSELECT TO_CHAR(completion_time, YYYY-MM-DD) AS day, ROUND(SUM(blocks * block_size) / 1024 / 1024 / 1024, 2) AS total_gb FROM v$archived_log WHERE completion_time SYSDATE - 7 AND archived YES AND deleted NO GROUP BY TO_CHAR(completion_time, YYYY-MM-DD) ORDER BY day;这条SQL算出来的每天归档总量可以直接用来判断是否有“非典型”的大写入日。比如一个系统平时一天归档50GB某一天突然变成300GB那就一定发生了什么。至于是什么就需要结合SQL分析和业务变更记录去进一步排查。3.4 容易忽略的坑DG备库、RMAN删除和归档目的用v$archived_log统计时有几个大坑必须注意。DG环境里备库也可能会产生归档如果是MAXIMUM PERFORMANCE模式下备库的RFS进程不产生本地归档但部分场景会产生主库的v$archived_log只统计主库自身生成的归档。一旦通过RMAN做了DELETE ARCHIVELOGv$archived_log里的记录还在只是标记为DELETED YES。统计时一定要过滤掉DELETEDNO否则会把已经清理的历史数据也算进去。如果数据库开启了db_flashback_retention_target或配置了闪回日志闪回日志的写入量和归档日志没有直接关系别把它们混为一谈。4. 高峰期“重日志SQL”定位实操三步走从归档日志挖出嫌疑SQL高峰时段锁定了接下来要解决的核心问题是这段时间里到底哪类SQL在产生大量重做日志。这一步没法直接读取归档日志里的SQL文本后面会说为什么但有套三步走的打法非常实用。4.1 第一步用v$sqlarea抓“瞬时重做量”大户v$sqlarea自带一个字段叫REDO_SIZE记录的是某条SQL从实例启动以来累计产生的redo字节数。要定位某个时间窗口的“重日志SQL”思路是先看当前都在跑什么SQL再结合时间和执行次数的变化趋势判断。SELECT sql_id, executions, ROUND(redo_size / 1024 / 1024 / 1024, 2) AS redo_gb, ROUND(elapsed_time / 1000000 / 60, 2) AS elapsed_min, sql_text FROM v$sqlarea WHERE redo_size 0 AND last_active_time TO_DATE(2025-01-06 14:00:00, YYYY-MM-DD HH24:MI:SS) AND last_active_time TO_DATE(2025-01-06 15:00:00, YYYY-MM-DD HH24:MI:SS) ORDER BY redo_size DESC FETCH FIRST 20 ROWS ONLY;注意REDO_SIZE是累计值不是这个小时内的增量所以排序结果反映的是“历史上累计产生redo最多的SQL”不一定就是这个小时的高峰元凶。但换个角度想累计redo量最大的SQL往往就是那些在高峰期被高频执行、或者单次执行就产生巨量redo的常驻SQL优先级依然很高。更精准的做法是隔一段时间采样redo_size做差值比如14:00采一次14:05再采一次看哪些SQL的redo_size在5分钟内疯涨。这个方法不需要额外工具定时跑两次采集脚本再做个差集就行。4.2 第二步用DBA_HIST_SQLSTAT回溯历史窗口如果高峰已经过去了v$sqlarea里的信息可能已经被挤出去这时候就要靠DBA_HIST_SQLSTAT。这个视图是AWR基础组件的一部分即使没有额外授权通常也能查询部分版本可能受限需要实测确认它记录了每个快照窗口内SQL的执行统计包括EXECUTIONS_DELTA窗口内执行次数增量ROWS_PROCESSED_DELTA处理行数增量CPU_TIME_DELTA、ELAPSED_TIME_DELTACPU和耗时增量IOWAIT_DELTAIO等待时间DELTA_READ_IO_BYTES、DELTA_WRITE_IO_BYTES读写字节数通过DBA_HIST_SNAPSHOT关联出你要分析的时间窗口比如下午2点到3点之间的快照ID然后比较窗口前后两个快照的增量数据SELECT s.snap_id, s.begin_interval_time, s.end_interval_time, sq.sql_id, sq.executions_delta, sq.rows_processed_delta, ROUND(sq.elapsed_time_delta / 1000000 / 60, 2) AS elapsed_min, sq.cpu_time_delta / 1000000 AS cpu_sec FROM dba_hist_sqlstat sq JOIN dba_hist_snapshot s ON s.snap_id sq.snap_id AND s.instance_number sq.instance_number WHERE s.begin_interval_time TO_DATE(2025-01-06 13:00:00, YYYY-MM-DD HH24:MI:SS) AND s.end_interval_time TO_DATE(2025-01-06 15:00:00, YYYY-MM-DD HH24:MI:SS) AND sq.executions_delta 0 ORDER BY sq.elapsed_time_delta DESC FETCH FIRST 30 ROWS ONLY;这里的核心价值在于你可以把时间窗口限定得非常精确精准到快照覆盖的1小时里每条SQL的真实消耗。再用DBA_HIST_SQLTEXT把SQL_ID翻译成文本高峰元凶立刻浮出水面。4.3 第三步用LogMiner补充DML维度的“变更内容”如果目标是搞清楚高峰期到底改了哪些表、哪些行那就必须上LogMiner了。LogMiner是Oracle自带的日志解析工具可以直接分析归档日志里的DML和DDL操作。基础用法分三步先DBMS_LOGMNR.ADD_LOGFILE添加要分析的归档日志再DBMS_LOGMNR.START_LOGMNR启动分析最后查询V$LOGMNR_CONTENTS获取解析结果。限制分析窗口的示例EXEC DBMS_LOGMNR.ADD_LOGFILE(LOGFILENAME /arch/1_12345_1111111111.arc, OPTIONS DBMS_LOGMNR.NEW); EXEC DBMS_LOGMNR.START_LOGMNR(STARTTIME TO_DATE(2025-01-06 14:00:00, YYYY-MM-DD HH24:MI:SS), ENDTIME TO_DATE(2025-01-06 15:00:00, YYYY-MM-DD HH24:MI:SS), OPTIONS DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG); SELECT seg_owner, seg_name, operation, COUNT(*) FROM v$logmnr_contents GROUP BY seg_owner, seg_name, operation ORDER BY COUNT(*) DESC;这段SQL跑出来就能看到高峰期哪些表的INSERT/UPDATE/DELETE最多。再进一步可以按SQL_REDO字段的相似度聚合找到重复执行最多的变更语句。这里有个经验要分享一下LogMiner非常吃资源在高峰期归档日志好几GB的环境里跑全量解析可能把库拖垮。我的建议是只挑高峰时段的那一两个归档日志文件解析别贪多能说明问题就够了。4.4 为什么不能直接从归档日志里“读”出SQL文本很多人第一次接触归档日志分析时会有一个预期能不能直接打开归档日志文件像看SQL文本一样看到每条语句这个预期是错的。Oracle的redo log里存的不是SQL文本而是变更向量change vector是数据块修改前后的字节级描述。它只记录“哪个文件哪个块哪个偏移量被改成了什么”不记录“哪条SQL导致的这次修改”。所以从归档日志本身能还原的是“哪些表被改了”“哪一行被改了”这类物理变更事实而不是完整的SQL语句。想要SQL文本必须回到SQL解析相关的内存结构v$sql或者历史的AWR快照数据。这也解释了为什么上面第三步要和第二步配合使用LogMiner告诉你“改了哪些表”DBA_HIST_SQLSTAT告诉你“哪些SQL在跑”两者一交叉结论就八九不离十了。5. 踩坑实录归档日志分析中最容易翻车的五个场景做这一行久了各种“看着合理但实际翻车”的情况见得不少。下面五个场景是我用归档日志做分析时最常遇到的坑写出来给大家提个醒。5.1 坑一日志切换频繁不代表有“高峰”也可能是日志组太小这是我早期犯过的错误。某个系统每天归档量不大但日志切换次数特别多一小时切了二十几次我当时断言系统有异常写入高峰。后来一看配置文件在线日志组才配了200MB随便一个批量更新就能切好几次。切换频率高是“日志被写满”的信号不代表“写入量异常大”两者要分开看。判断标准很简单算下每个日志组的大小再把切换次数乘上去换算成实际写入量和基线对比才有意义。5.2 坑二归档日志被提前删除历史窗口直接没法查归档日志不是永久存在的。很多生产环境为了省空间设置了较短的归档保留策略或者定时任务清理。等你需要复盘一个月前的某次高峰时归档早就没了。这不是归档分析方案本身的问题而是规划问题。建议是把归档日志至少保留60天以上或者定期把归档转储到廉价的存储介质上留底。尤其是等保合规场景里日志留存本身就有要求单纯从运维排查的角度这个保留期也不能太短。5.3 坑三V$LOGMNR_CONTENTS查询结果过大直接把临时表空间撑爆LogMiner分析一小时的高峰归档解析出来的V$LOGMNR_CONTENTS可能上亿行。如果你直接用SQL去聚合临时表空间瞬间就能被打满。我遇到过最夸张的一次一个300MB的归档解析出来的变化记录有两千多万行直接导致临时表空间用尽影响到同实例其它业务会话。对策有两个。一是精细过滤START_LOGMNR时就带上SEG_NAME只分析目标表或者在查询时只取必要的列比如只取SEG_NAME和OPERATION做聚合不取SQL_REDO这种超长字段。二是把结果导到外部表或临时表再分批处理避免一次性大聚合。5.4 坑四RAC环境里漏看某个实例的归档日志RAC集群环境下每个实例有自己的归档日志序列v$log_history和v$archived_log在不同实例上查到的内容可能不同。直接从单个实例查容易漏掉其它实例的归档记录。正确做法是通过GV$视图或者从共享存储/集中归档目录里统一查看。比如RAC环境下查跨实例的日志切换情况用GV$LOG_HISTORYSELECT inst_id, TO_CHAR(first_time, YYYY-MM-DD HH24) AS hour, COUNT(*) AS switch_cnt FROM gv$log_history WHERE first_time SYSDATE - 1 GROUP BY inst_id, TO_CHAR(first_time, YYYY-MM-DD HH24) ORDER BY inst_id, hour;如果不加inst_id维度分析结果会被多实例的数据搞乱可能导致误判。5.5 坑五误把DDL操作当成业务高峰归档日志忠实记录一切产生redo的行为包括DDL。凌晨的一次大表ALTER TABLE MOVE产生的归档量可能比整个白天的业务DML还大。如果你只看日志切换曲线很容易把一次运维操作误判成“异常业务高峰”。所以每次看到异常的归档高峰第一件事不是去查SQL而是先看时间段内是否有DDL操作、是否有索引重建、是否有数据加载任务。可以通过DBA_HIST_SQLSTAT的COMMAND_TYPE字段2表示INSERT3表示UPDATE7表示DELETE而DDL命令类型为85-90等快速排查。这个思路上升到方法论就是先排除运维噪声再谈业务分析。6. 深入一步把“归档日志分析”变成日常巡检的常态化手段每次都是出了事故才去翻归档我觉得太被动了。归档日志分析完全可以做成本常态化的巡检手段提前发现问题。6.1 定时采集用一张表记录每日归档趋势我在实践中维护过一张ARCH_DAILY_TREND表每天凌晨通过DBMS_SCHEDULER跑一个存储过程把前一天每小时的切换次数、归档量、归档文件数都记录进去持续积累。CREATE TABLE arch_daily_trend ( stat_day DATE, stat_hour NUMBER(2), switch_cnt NUMBER, arch_files_cnt NUMBER, arch_gb NUMBER(10,2), create_time DATE DEFAULT SYSDATE );存储过程里的核心逻辑就是每日对v$log_history和v$archived_log做一次按小时汇总然后写入这张表。这样积累两三个月后这套系统的工作负载基线就非常清晰了。以后再遇到性能问题直接看这张表哪天的哪个小时明显偏离基线一目了然。6.2 异常检测让数据自己“报警”有了基线和趋势表就可以设置简单的异常判断规则。比如连续3天同一时段切换次数超过两周均值2倍就触发告警。这个阈值不需要多复杂的算法因为归档日志的变化本身就够平滑异常通常是剧烈的。平时一小时切8次今天突然一小时切30次这就已经是足够强的信号了。用SQL就能做这种检测SELECT a.stat_day, a.stat_hour, a.switch_cnt, b.avg_switch_cnt FROM arch_daily_trend a JOIN (SELECT stat_hour, AVG(switch_cnt) AS avg_switch_cnt FROM arch_daily_trend WHERE stat_day TRUNC(SYSDATE) - 21 AND stat_day TRUNC(SYSDATE) GROUP BY stat_hour) b ON a.stat_hour b.stat_hour WHERE a.stat_day TRUNC(SYSDATE) - 1 AND a.switch_cnt b.avg_switch_cnt * 2 ORDER BY a.stat_hour;如果这个查询有返回结果说明昨天有某个时段严重偏离了正常模式值得人工介入。6.3 结合存档目录做容量预测归档日志分析除了定位高峰还能做容量预测。通过v$archived_log统计近30天每天的归档总量算日均增长结合RMAN备份策略就能估算磁盘到底需要预留多少空间。这个数据在规划存储扩容、做容量评估时特别有用。尤其是那些磁盘使用率达到80%以上就有人工告警的环境提前预估能省掉大半夜扩容的痛苦。生产线上的教训是归档空间被打满会导致数据库直接挂起这一点再怎么强调都不过分。7. 和一些“高档工具”的取舍对比聊到这里肯定有人会问既然有那么多商业化监控工具和云平台监控为什么还要自己捣鼓这套归档分析的土办法我觉得两者不是替代关系是互补关系。商业化监控工具的强项是自动化、可视化、多维度联动适合做全链路监控。但它们的代价是成本高、部署繁琐而且一旦工具的采集粒度不够面对突发问题照样抓瞎。自建归档分析的强项是零成本、无侵入、依托数据库本身而且它观测的是“真实发生过的物理事实”这是任何通过采样推断的监控方式都比不上的。用一个表格对比更直观对比维度商业化监控工具归档日志手工分析成本高按节点或按容量收费零成本使用已有视图和日志部署复杂度需要装agent、配采集、维护链路建几张表写几条SQL数据可信度基于采样可能丢瞬时高峰物理事实完整记录每次变更分析实时性准实时或分钟级事后分析适合复盘定位SQL的能力强文本级定位直接准确间接定位需多层交叉验证长期趋势分析强自带大盘和报表需要自己建基线表积累我自己在实际工作中的思路是日常巡检靠工具深度复盘靠归档日志。一旦工具给出的结果和归档日志反映的情况对不上我无条件相信归档日志。因为工具可能采样丢失但数据库的redo不会骗人。8. 写在最后的实用清单这一节是纯粹的干货整理把前面所有的操作浓缩成一份可以抄作业的检查清单。做归档日志分析时直接按这个顺序走。确认数据库处于ARCHIVELOG模式SELECT log_mode FROM v$database;。如果不是那整个方案基础都不成立。画出过去一周的按小时日志切换曲线找到异常时段。查询异常时段内是否安排了DDL、索引重建、数据迁移等运维操作排除噪声。如果异常时段是真实业务压力用v$sqlarea和DBA_HIST_SQLSTAT交叉定位该时段的高消耗SQL。需要了解具体改了哪些表时用LogMiner只解析该时段的一两个归档文件避免资源过度消耗。记录当天的异常现象、归档指标、SQL分析结果到自建的排查日志表形成知识库。这套流程我已经用了很多年。它不复杂全都是Oracle自带的功能但确实一次次帮我找到了那些“藏得很深”的SQL问题。归档日志就像数据库的一个黑匣子平时安安静静待着关键时刻把系统发生过的一切如实告诉你。能不能听懂它说的话就看你是不是真的愿意耐下性子去翻那些看起来冷冰冰的.arc文件了。