
Oracle Data GuardDG和 Active Data GuardADG是生产环境里最常见的 Oracle 主备高可用组合。我见过太多团队把备库搭好后就当“保险柜”放着平时不看等真正需要切换库的时候才发现日志延迟几分钟、归档有 gap、传输链路早就断了。日常巡检不是走过场而是把“备库到底能不能接管”这件事用一项项数据固定下来。这篇指南面向 DBA、运维开发也适合刚入手 Oracle 高可用体系的同学我会先把 DG/ADG 的底层逻辑讲清楚再给一套能直接抄的巡检检查单和 SQL 脚本最后把高频故障和排查顺序一起整理出来。1. 巡检前必须搞懂DG/ADG 到底在保护什么1.1 主备之间的三件套日志传送、日志应用、角色切换DG 的核心链路可以拆成三段主库产生 redo 日志通过网络传送到备库备库再把日志应用到自己的数据文件上。整个过程由主库的 LGWR 或 ARCH 进程负责发送备库的 RFS 进程负责接收MRP 进程负责应用。ADG 是 DG 的增强形态不同点在于备库处于只读打开状态日志几乎是实时应用同时还能对外提供查询和报表能力。巡检之前建议先和团队对一遍关键参数避免拿错标准LOG_ARCHIVE_CONFIGDG_CONFIG(主库db_unique_name,备库db_unique_name)LOG_ARCHIVE_DEST_2SERVICE备库服务名 LGWR SYNC AFFIRMFAL_SERVER主库服务名STANDBY_FILE_MANAGEMENTAUTO保护模式和传输方式直接相关。SYNC AFFIRM通常用于最大保护或最大可用模式主库提交前必须等备库确认收到日志ASYNC一般配合最大性能模式主库不会因为备库慢而卡住。很多保单库配的是SYNC但业务高峰期主库提交变慢巡检时如果只看到延迟小就没问题我建议同时确认保护模式是不是你想用的那个模式。1.2 巡检到底在盯什么RPO、RTO、可用性DG/ADG 的日常巡检本质上是在回答四个问题主库最新的日志传到备库没有备库把日志应用完没有中间有没有缺口一旦切换需要多久。对应到具体指标上就是redo 传输延迟transport lag、日志应用延迟apply lag、归档缺口gap、备库磁盘和快速恢复区剩余空间、告警日志里的 ORA- 错误数。再往深一层还要关心备库当前是否处于正确的打开模式ADG 模式下备库open_mode应该是READ ONLY WITH APPLY如果显示MOUNTED说明 ADG 没有被激活如果显示READ WRITE那基本是配置错乱或者备库被误激活了这种场景我碰到过不止一次。1.3 巡检前先学会看主备状态输出无论用什么工具巡检第一眼看的都是v$database视图。主库和备库的查询结果差异很大SELECT database_role, protection_mode, protection_level, open_mode, db_unique_name FROM v$database;主库database_role是PRIMARYopen_mode是READ WRITE。传统 DG 备库database_role是PHYSICAL STANDBYopen_mode是MOUNTED。ADG 备库database_role是PHYSICAL STANDBYopen_mode是READ ONLY WITH APPLY。protection_level这列容易被忽略但只要主备之间的网络抖动过protection_level可能从MAXIMUM AVAILABILITY降到RESYNCHRONIZATION或MAXIMUM PERFORMANCE这时候就需要人工介入。巡检时看到MAXIMUM AVAILABILITY不代表一直健康要多看一眼 level 列的实时值。2. 照着做就行一套标准的 DG/ADG 巡检检查单2.1 日检状态快照和延迟指标日检不需要太复杂但要保证能发现“备库半残废”的情况。我在生产环境里每天固定跑三条 SQL-- 主备角色、模式、打开状态 SELECT database_role, protection_mode, open_mode, db_unique_name FROM v$database; -- 日志传输与应用延迟 SELECT name, value, unit, time_computed, datum_time FROM v$dataguard_stats WHERE name IN (transport lag, apply lag); -- 归档缺口 SELECT * FROM v$archive_gap;v$dataguard_stats在备库上查最准。正常环境下transport lag和apply lag都是00 00:00:00或者00 00:00:01如果有几十秒甚至几分钟的数值先别慌可能是主库正在跑批量任务生成了大量归档。但如果延迟持续增长而不是回落就要马上查网络和备库磁盘。我自己的经验是日检还要顺手看一眼v$standby_log确认备库的在线日志组状态正常SELECT thread#, sequence#, applied, status FROM v$standby_log;applied为YES是最理想的状态如果出现NO且持续不变化说明备库的 MRP 进程可能已经停了。status列的ACTIVE表示当前正在写入或读取UNASSIGNED是空闲组都算正常。2.2 周检归档连续性、磁盘与恢复区周检的重点是确认归档链路没有“慢性失血”。只查延迟还不够数据文件里不能有空洞因为备库追日志卡住往往是从一个缺失的归档开始的。检查归档连续性的常用 SQLSELECT thread#, MAX(sequence#) FROM v$archived_log GROUP BY thread#; SELECT thread#, sequence#, applied, completion_time FROM v$archived_log WHERE sequence# (SELECT MAX(sequence#) - 5 FROM v$archived_log) ORDER BY sequence#;如果备库的v$archived_log最大序列号和主库差得越来越远说明传输一直在落后。更严重的是当v$archive_gap返回了行说明备库已经不知道自己缺了哪些归档需要立刻处理。磁盘空间方面先查操作系统层面再查数据库内部# 主机磁盘空间 df -h数据库快速恢复区FRA用这个SELECT name, space_limit, space_used, ROUND(space_used / space_limit * 100, 2) AS pct_used FROM v$recovery_file_dest;FRA 使用率飙到 90% 以上通常意味着归档删除策略没生效或者 RMAN 没有及时清理过期的备份和归档。备库磁盘被打满是 DG 巡检里最常见的事故之一因为 RFS 进程没法写新日志传输会立刻报错严重时备库直接脱离保护。2.3 月检角色切换演练与性能对比月检最重要的是做一次主备切换演练。Switchover 是可以无缝进行的计划内切换它能验证备库数据是否完整、参数是否合理、应用连接是否能及时转移。很多团队不敢切怕出事故但正是因为平时不演练真正出故障时才发现一堆隐藏问题。切换演练前至少检查这几项确认主备的db_unique_name、服务名、监听配置都正确。确认归档 gap 为 0。确认SWITCHOVER_STATUS在主库和备库都处于TO STANDBY或SESSIONS ACTIVE状态。提前通知业务方切换期间会有短暂会话中断。SELECT switchover_status, database_role, open_mode FROM v$database;月检的另一个价值在于性能对比。主库跑批后的次日备库的延迟曲线、归档生成量、磁盘增长速率都可以和上月同期对比提前发现业务量增长给备库带来的压力。顺便把主备两端的关键初始化参数导出存档之后出了问题可以快速比对是不是有人改过参数。3. 可以直接抄的巡检 SQL 脚本集合3.1 一套够用的巡检 SQL不用装额外客户端日常巡检我习惯用 SQL*Plus 跑脚本不依赖图形化工具也不需要在每台机器上部署 OEM Agent。把脚本存成一个.sql文件连上备库执行就行输出格式固定方便落盘留痕。一个比较完整的巡检脚本长这样-- DG_ADG_CHECK.sql SET PAGESIZE 200 SET LINESIZE 200 SET FEEDBACK OFF PROMPT 1. 主备角色和状态 SELECT database_role, protection_mode, protection_mode AS p_mode, open_mode, db_unique_name, inst_id FROM v$database, gv$instance ORDER BY inst_id; PROMPT 2. 传输/应用延迟 SELECT name, value, unit, time_computed FROM v$dataguard_stats WHERE name IN (transport lag, apply lag); PROMPT 3. 归档缺口 SELECT * FROM v$archive_gap; PROMPT 4. 当前应用日志序列 SELECT thread#, MAX(sequence#) AS current_sequence FROM v$archived_log GROUP BY thread#; PROMPT 5. 快速恢复区使用率 SELECT name, space_limit, space_used, ROUND(space_used / space_limit * 100, 2) AS pct FROM v$recovery_file_dest; EXIT;这套脚本在备库上执行最合适因为延迟和应用状态的实时数据都在备库。注意v$database在 RAC 环境下每个节点都能查视图返回的是整个数据库的信息不是单实例信息如果要看每个实例的状态就需要关联gv$instance。习惯用 TOAD 的朋友也可以直接把脚本存成.sql文件连上备库跑一次结果用表格视图看更直观。我偶尔也会用 TOAD 的会话监控面板快速确认备库当前有哪些活动会话它能补足纯 SQL 看不到的部分。3.2 用存储过程把巡检逻辑固化下来巡检脚本多了以后输出分散、格式不统一后续很难自动判断健康状态。我建议把巡检项目的几项关键指标写进存储过程结果统一写入一张日志表这样既可以查历史趋势也能给外部监控平台提供数据来源。一个简单的存储过程示例CREATE TABLE dg_check_log ( check_time TIMESTAMP, db_name VARCHAR2(30), transport_lag VARCHAR2(60), apply_lag VARCHAR2(60), gap_count NUMBER, ok_flag VARCHAR2(10) ); CREATE OR REPLACE PROCEDURE p_dg_check AS v_dbname VARCHAR2(30); v_tlag VARCHAR2(60); v_ulag VARCHAR2(60); v_gap NUMBER : 0; v_ok VARCHAR2(10) : Y; BEGIN SELECT db_unique_name INTO v_dbname FROM v$database; SELECT value INTO v_tlag FROM v$dataguard_stats WHERE name transport lag; SELECT value INTO v_ulag FROM v$dataguard_stats WHERE name apply lag; SELECT COUNT(*) INTO v_gap FROM v$archive_gap; IF v_gap 0 OR INSTR(v_ulag, 00:00:) 0 THEN v_ok : N; END IF; INSERT INTO dg_check_log VALUES (SYSTIMESTAMP, v_dbname, v_tlag, v_ulag, v_gap, v_ok); COMMIT; END; /这里要说明一下v$dataguard_stats的value列是字符串比如00 00:00:01直接用数值判断不太方便示例里用INSTR判断是否落在秒级比较粗糙。真正落地时建议用REGEXP_SUBSTR提取天数/小时/分钟或者干脆在查询时转成秒数存库。存储过程的好处不只是“看起来正规”。把巡检逻辑固化成一个固定入口之后后续加检查项、调阈值、出报告都只需要改一个对象不用再到处改脚本。生产环境里我通常还会加一个dg_check_result表专门记录某个指标的当日最大值和平均值给月报和趋势分析用。3.3 定时任务与 Python 自动化告警SQL 脚本和存储过程适合人工执行但巡检想要真正落地必须有自动化和告警。凌晨两三点让 DBA 爬起来跑 SQL 不现实常见做法是用操作系统定时任务调用脚本把结果入库再由 Python 脚本读取并发送通知。Python 连接 Oracle 查询数据是现成方案以python-oracledb为例import oracledb conn oracledb.connect( usersys, passwordpassword, dsn192.168.1.10:1521/STANDBY, modeoracledb.AUTH_MODE_SYSDBA ) cur conn.cursor() cur.execute( SELECT name, value FROM v$dataguard_stats WHERE name IN (transport lag, apply lag) ) for name, value in cur.fetchall(): print(f{name}: {value}) cur.execute(SELECT COUNT(*) FROM v$archive_gap) gap cur.fetchone()[0] print(fgap count: {gap}) conn.close()生产环境里不要把密码明文写在脚本里建议用 Oracle Wallet 或外部配置中心管理连接凭据。Python 脚本可以做三件事跑巡检 SQL、把结果转换成统一格式、超过阈值时通过钉钉/邮件/短信发出来。4. ADG 打开只读后巡检多出来的几个坑4.1 ADG 查询和日志应用抢资源不能只看延迟传统 DG 备库是MOUNTED状态没有外部会话资源相对可控ADG 一旦对外提供只读访问就是“边应用日志边跑查询”查询的 SQL 如果写得烂会抢占 CPU 和 IO间接拖慢 MRP 进程导致延迟升高。我在实际环境中见过最典型的场景报表团队在 ADG 上跑一个大表全扫描把存储 IO 打满主库传来的日志排队应用apply lag从秒级涨到小时级。这个时候不能直接怪网络要查的是备库上的活动会话和等待事件。快速定位备库资源消耗的 SQLSELECT event, COUNT(*) AS cnt FROM v$session WHERE status ACTIVE GROUP BY event ORDER BY cnt DESC FETCH FIRST 10 ROWS ONLY;如果排在前面的是db file sequential read、direct path read大概率是查询导致 IO 压力大如果是RFS相关等待那更多是网络或写入路径问题。单靠一条延迟曲线判断不了根因一定要结合等待事件看。4.2 临时表空间和表空间增长ADG 备库同样会爆ADG 备库会处理排序、物化视图刷新、复杂关联查询这些操作同样需要临时表空间。很多人以为备库不写数据就不用看临时表空间结果某个大查询直接报 ORA-01652把会话全部卡死。巡检时加上临时表空间使用情况SELECT tablespace_name, current_size, max_size, ROUND(current_size / NULLIF(max_size, 0) * 100, 2) AS pct_used FROM dba_temp_free_space ORDER BY pct_used DESC;另外ADG 备库应用日志时会继续写入数据文件如果某个表空间剩余空间不足且没有开启自动扩展日志应用一样会被卡住。主库跑大批量夜间任务时备库的数据文件增长往往发生在次日凌晨。建议周检时对比一周内每个表空间的增长量SELECT tablespace_name, SUM(bytes) / 1024 / 1024 AS total_mb, MAX(bytes) / 1024 / 1024 AS max_file_mb FROM dba_data_files GROUP BY tablespace_name ORDER BY total_mb DESC;4.3 ADG 上的慢 SQL 不只是性能问题还会拖垮日志应用ADG 巡检里最容易忽略的是慢 SQL 治理。一条跑了十分钟的统计 SQL在主库也许只是消耗资源在备库上它会让日志应用长时间等待 IO最终体现为 apply lag 不断增加。发现备库上的 Top SQL可以用v$sql和v$sqlstatsSELECT sql_id, sql_text, elapsed_time, buffer_gets, executions FROM v$sql WHERE parsing_schema_name REPORT_USER ORDER BY elapsed_time DESC FETCH FIRST 10 ROWS ONLY;拿到问题 SQL 后优化方向通常是这几条减少全表扫描增加合适索引尽量避免在 ADG 上跑超大关联查询和多次大排序。有些报表逻辑可以改成物化视图在主库刷新备库直接读这样既满足实时性要求又减少对日志应用的干扰。早期版本没有FETCH FIRST语法包一层ROWNUM也能实现同样的分页效果。5. 常见故障排查与速查表5.1 备库应用中断与归档 GAP 处理备库应用中断的典型报错是 ORA-00313、ORA-00328。ORA-00313 通常表示无法打开的日志文件ORA-00328 表示日志序列号状态不一致。两个错误一起出现时要优先确认备库当前缺哪些归档以及文件路径是否可达。排查顺序建议看v$archive_gap是否有数据行有则先补归档。看v$recover_file确认哪个数据文件需要恢复。看v$standby_log状态确认当前联机日志没有被破坏。如果缺的是单个归档可以用主库的备份把它注册进备库ALTER DATABASE REGISTER LOGFILE /oracle/archive/1_12345_1234567890.arc;如果缺失的归档已经无法找回备库和主库的差距又在可接受范围内可以用增量备份从主库恢复备库再继续应用日志。这种方法比完全重建备库快得多也是我遇到长期 gap 时优先采用的手段。5.2 传输链路故障监听和网络排查DG 的日志传输依赖主备之间的网络和监听链路出问题时主库的v$archive_dest_status会看到ERROR列报出 TNS 错误。最常见的有这几个错误码含义常见原因ORA-12541TNS:no listener监听没起来或端口不通ORA-12514listener 不识别服务名SERVICE_NAME配置错误ORA-12545目标主机不可达DNS 解析或网络路由问题对应排查动作先确认备库监听是否正常执行lsnrctl status再在备库本机执行tnsping主库服务名然后看监听日志listener.log尾部有没有异常连接记录。端口占用可以通过系统命令查Windows 上常用netstat -ano | findstr 1521Linux 上用ss -lntp。如果 DG 传输侧报的是 ORA-12514多半是LOG_ARCHIVE_DEST_n里的SERVICE名称和备库注册的SERVICE_NAME不一致修改参数后要执行ALTER SYSTEM SET LOG_ARCHIVE_DEST_2SERVICESTANDBY01 LGWR SYNC AFFIRM SCOPEBOTH;5.3 延迟高怎么判断是网络、磁盘还是业务量延迟高的排查要分层定位先看transport lag如果这个值本身就高问题在主库到备库的传输链路如果transport lag正常而apply lag高问题在备库应用侧。备库应用慢的原因通常是磁盘 IO 慢、日志文件所在文件系统空间不足、查询和日志应用抢 IO。我遇到过一种特殊情况备库的磁盘虽然没满但用的是性能很差的云盘高峰期大批量归档写入直接把 IO 打满。所以巡检备库时不要只看空间还要关注 IO 延迟。另外可以用历史归档生成量估算某个时段的 redo 产生速度SELECT thread#, TO_CHAR(first_time, YYYY-MM-DD HH24) AS hour, COUNT(*) AS log_count, ROUND(SUM(blocks * block_size) / 1024 / 1024, 2) AS size_mb FROM v$archived_log WHERE first_time SYSDATE - 3 GROUP BY thread#, TO_CHAR(first_time, YYYY-MM-DD HH24) ORDER BY 1, 2;通过对比历史数据能确认当前延迟是偶发还是业务增长带来的持续压力。如果是明显的主库大事务导致日志量猛增可以临时调整传输方式或延迟告警阈值避免误报刷屏如果是备库硬件瓶颈该扩容就扩容。5.4 顺手补一刀安全基线巡检DG/ADG 巡检里我一般会加一条安全基线的检查不用很重但必须记录留痕。重点看密码文件权限和远程登录账号SELECT * FROM v$pwfile_users ORDER BY username;确认只有真正需要SYSDBA权限的账号保留在这里。有些环境从 11g/12c 升级到 19c 后没有重新生成密码文件备库远程登录时会出现密码文件不匹配的问题表现为ORA-28040: No matching authentication protocol或ORA-01017。遇到这种情况主备两侧用orapwd重新生成一致的文件再重启监听验证。安全基线检查不需要每天做可以放到周检或月检里但结果要归档。等保测评时经常需要提供巡检记录和权限变更记录平时留好关键时刻不用临时补数据。6. 巡检记录模板与阈值参考6.1 巡检报告怎么留痕巡检结果一定要落表和留痕否则等于没巡检。我用的巡检记录表很简单字段少但每次都能回答“当时备库是什么状态”| 巡检时间 | 角色 | 保护模式 | transport lag | apply lag | gap 数 | FRA 使用率 | 错误数 | 结论 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-06-01 08:00 | PRIMARY | MAX AVAILABILITY | — | — | 0 | 62% | 0 | 正常 | | 2024-06-01 08:00 | PHYSICAL STANDBY | MAX AVAILABILITY | 00 00:00:01 | 00 00:00:01 | 0 | 58% | 0 | 正常 |记录表可以手动填也可以直接由自动化脚本写入dg_check_log表月底导出成 Excel。有历史数据之后延迟有没有恶化、空间增长是不是异常、错误是不是反复出现一眼就能看出来。6.2 阈值怎么定别把阈值压得太死阈值没有统一标准要结合业务容忍度和硬件情况设定。我给出一个常用的基线供参考指标正常关注严重transport lag 30 秒30 秒 ~ 5 分钟 5 分钟apply lag 1 分钟1 分钟 ~ 30 分钟 30 分钟gap 数01 ~ 3 3FRA 使用率 80%80% ~ 90% 90%备库磁盘剩余 20%10% ~ 20% 10%阈值可以分成两级黄色阈值提醒人工关注红色阈值直接触发电话告警。我个人的经验是告警不要过度追求灵敏否则会变成“狼来了”反而让真正的问题被淹没。宁可先跑一周看基线再根据实际情况调整阈值。6.3 从日检走向自动化的节奏巡检自动化的路径我的建议是分三步走。第一步把人工执行的 SQL 脚本固定下来哪怕每天手动跑一遍也要保证有输出第二步用存储过程把结果写入日志表开始积累历史数据第三步用 Python 或第三方监控工具读取日志表按照阈值发告警。每一步都不难难的是坚持执行。如果你已经上了 OEM/EM 这类工具也可以把 SQL 巡检作为补充手段用于快速获取原生数据。自动化的核心目标只有一个把“备库是否可用”变成每天可验证、可追溯的数据而不是等到灾难发生时才去猜。最后再分享一个我个人的习惯巡检脚本不只是每天跑一遍每次跑完要把输出保存下来并且定期做一次真实的主备切换演练。演练永远比巡检更能暴露问题。切换后如果主备能顺利互换角色、日志能无缝追平、应用连接能自动恢复巡检单上的那串数字才有意义。这是整个 DG/ADG 运维里我最看重的一句话。