跳过正文

Oracle 19c RAC 到单实例 ADG 实战:Active Duplicate、双 Thread Redo 与归档清理

Greatfinish
作者
Greatfinish
记录 Oracle、PostgreSQL、达梦、Linux、存储与生产环境故障处理经验。
目录

前言
#

这套环境使用 RMAN Active Duplicate,将两节点 RAC 主库 cmsjs 复制为单实例物理备库 cmsjsyddg,再通过 ASYNC 传输 redo,以 READ ONLY WITH APPLY 模式提供备库查询。

主库使用 ASM,备库使用文件系统。主库有两个 redo thread,备库虽然只有一个实例,仍要接收并应用两个 thread 的 redo。备库内存也小于任一主库节点,复制数据库时必须调整继承的参数。

文中的主机、路径、补丁号和运行结果来自本次部署记录。修订后的部署流程尚未完整实库复测;归档清理脚本 v1.1 已完成一次现场预览,结果见第 9.2 节,实际删除分支仍需验证。本文未覆盖数据库软件安装、Broker、自动故障转移及主备切换演练。

1. 环境与部署边界
#

1.1 主备配置
#

项目RAC 主库单实例备库
DB_NAMEcmsjscmsjs
DB_UNIQUE_NAMEcmsjscmsjsyddg
主机名sjdb1sjdb2cmsjsdg2
实例名cmsjs1cmsjs2cmsjsyddg
redo thread1、2接收主库的 thread 1、2
Oracle 版本19.28.0.0.019.28.0.0.0
数据库存储ASM,+DATA文件系统 OMF
ORACLE_HOME/u01/app/oracle/product/19.3.0/dbhome_1/u01/app/oracle/product/19.3.0/db_1
物理内存每节点约 754 GB约 126 GB
保护模式MAXIMUM PERFORMANCE随配置核验

主备 Oracle Home 的补丁记录一致:

37962946;OCW RELEASE UPDATE 19.28.0.0.0
37960098;Database Release Update : 19.28.0.0.250715

ORACLE_HOME 路径中的 19.3.0 是目录名,实际版本以数据库查询和补丁清单为准。

网络用途主机名IP监听端口
RAC 节点 1sjdb110.87.180.10按实际节点监听配置
RAC 节点 2sjdb210.87.180.11按实际节点监听配置
节点 1 VIPsjdb1-vip10.87.180.12按实际节点监听配置
节点 2 VIPsjdb2-vip10.87.180.13按实际节点监听配置
SCANrac-scan10.87.180.141521
备库cmsjsdg210.87.201.281521

备库 /u01 总容量约 6 TB。数据文件目录为 /u01/oradata/cmsjsyddg,快速恢复区(FRA)为 /u01/fra/cmsjsyddg,FRA 配额设为 1000G。目录位于同一文件系统时会竞争空间,FRA 配额也不会替数据库预留磁盘。

1.2 redo 如何到达单实例备库
#

CMSJS RAC primary
  sjdb1 / cmsjs1 / thread 1 ──┐
                              ├── ASYNC redo ──> cmsjsdg2 / cmsjsyddg
  sjdb2 / cmsjs2 / thread 2 ──┘                    ├─ thread 1 的 SRL
                                                  ├─ thread 2 的 SRL
                                                  └─ MRP0 应用 redo
                                               READ ONLY WITH APPLY

物理备库与主库使用相同的 DB_NAME,用不同的 DB_UNIQUE_NAME 区分数据库。CLUSTER_DATABASE=FALSE 表示备库以单实例运行,不会把主库的两个 redo thread 合并成一个。

RFS 进程接收 redo 并写入备库重做日志(Standby Redo Log,SRL);恢复进程 MRP 协调 Redo Apply。实时应用可以在日志尚未归档时读取 SRL。本文的 ADG 配置让备库在只读打开的同时持续应用 redo。

1.3 执行前确认
#

开始部署前,Oracle Home 应已安装并完成补丁。复用前还需记录操作系统版本、平台兼容性、CDB/非 CDB 类型、数据库容量和 redo 生成量,现有现场记录未列出这些信息。

  • 主库已运行在 ARCHIVELOG 模式。Active Duplicate 期间保留所需归档,并安排主库 I/O、CPU 和网络可以承受的执行窗口。
  • 核对主备 Oracle Home 的 opatch lspatches。主库查询 DBA_REGISTRY_SQLPATCH 确认 SQL 补丁状态;备库可查询后再核对数据库记录。
  • 如主库为 CDB,在根容器执行文中的数据库级 SQL,并在备库初始化参数中设置 enable_pluggable_database=TRUE。PDB 打开状态和业务服务另行验收。
  • 如使用 TDE,先准备备库 keystore 和所需密钥。密码文件不能替代 TDE 密钥。
  • 检查现有归档目的地和 Broker 管理状态。本文使用手工参数配置,并以未占用的 LOG_ARCHIVE_DEST_2 为例;已有配置需合并,不能直接覆盖。
  • 使用 READ ONLY WITH APPLY 前确认相应 Active Data Guard 许可。未使用该选项的部署可停留在 MOUNTED 并运行 Redo Apply。Oracle 19c 物理备库管理

文中的 SQL 默认由指定数据库上的 oracle 用户以 SYSDBA 执行;目录授权和文件属主调整单独标明为 root 操作。

2. 主库检查与日志准备
#

2.1 记录数据库和 RAC 状态
#

在主库任一实例执行:

set lines 300 pages 100

select name, db_unique_name, dbid, cdb, database_role, open_mode,
       log_mode, force_logging, flashback_on, protection_mode,
       switchover_status
from v$database;

select inst_id, instance_name, host_name, thread#, status, version
from gv$instance
order by inst_id;

show parameter compatible
show parameter spfile
show parameter remote_login_passwordfile
show parameter dg_broker_start
show parameter db_recovery_file_dest
show parameter log_archive_dest

本次部署前记录如下:

NAME              CMSJS
DB_UNIQUE_NAME    cmsjs
DATABASE_ROLE     PRIMARY
OPEN_MODE         READ WRITE
LOG_MODE          ARCHIVELOG
FORCE_LOGGING     NO
FLASHBACK_ON      YES
PROTECTION_MODE   MAXIMUM PERFORMANCE

INST_ID  INSTANCE_NAME  HOST_NAME  THREAD#  STATUS
1        cmsjs1         sjdb1      1        OPEN
2        cmsjs2         sjdb2      2        OPEN

Duplicate 脚本包含 SPFILE 子句,执行前需确认主库使用 SPFILE。使用其他初始化方式时,不能原样套用该脚本。

2.2 开启 FORCE LOGGING
#

主库最初为 FORCE_LOGGING=NO。复制前启用强制日志,避免后续常规 NOLOGGING 操作留下物理备库无法通过 redo 重现的数据。

alter database force logging;
select force_logging from v$database;

本次返回 YES。该设置影响后续操作,不会补生成历史上未记录的 redo。Oracle 19c 创建物理备库

2.3 为两个 thread 分别配置 SRL
#

先核对在线重做日志(Online Redo Log,ORL):

select thread#, group#, round(bytes / 1024 / 1024) mb, members, status
from v$log
order by thread#, group#;

select group#, type, member from v$logfile order by group#, member;

本次每个 thread 有 3 组 ORL,每组 4096 MB、2 个 member。每个 thread 的 SRL 至少比 ORL 多 1 组,单组容量不小于对应来源的 ORL;本环境统一使用 4 GB。Oracle 19c redo 传输配置

Thread现有 ORL groupORL 单组大小新建 SRL groupSRL 单组大小
15、6、74096 MB11、12、13、144 GB
28、9、104096 MB15、16、17、184 GB

确认组号未被占用后,在主库创建 SRL。主库的 SRL 为后续角色转换做准备;Duplicate 也会使用已有定义在备库重建 SRL。

alter database add standby logfile thread 1 group 11 ('+DATA','+DATA') size 4G;
alter database add standby logfile thread 1 group 12 ('+DATA','+DATA') size 4G;
alter database add standby logfile thread 1 group 13 ('+DATA','+DATA') size 4G;
alter database add standby logfile thread 1 group 14 ('+DATA','+DATA') size 4G;
alter database add standby logfile thread 2 group 15 ('+DATA','+DATA') size 4G;
alter database add standby logfile thread 2 group 16 ('+DATA','+DATA') size 4G;
alter database add standby logfile thread 2 group 17 ('+DATA','+DATA') size 4G;
alter database add standby logfile thread 2 group 18 ('+DATA','+DATA') size 4G;

select thread#, group#, round(bytes / 1024 / 1024) mb, status
from v$standby_log
order by thread#, group#;

本次新建的 8 组 SRL 均为 UNASSIGNED。两个 member 都放在 +DATA 是现场配置,不构成两个独立磁盘组之间的故障隔离,底层冗余仍取决于 ASM 和存储设计。

2.4 设置 Data Guard 成员
#

在主库执行:

alter system set log_archive_config='DG_CONFIG=(cmsjs,cmsjsyddg)'
  scope=both sid='*';
alter system set standby_file_management='AUTO' scope=both sid='*';

DG_CONFIG 声明配置中的数据库成员。STANDBY_FILE_MANAGEMENT=AUTO 用于物理备库的数据文件自动管理;在当前主库设置它,是为以后成为备库做准备。

此时先不启用发往 cmsjsyddg 的远端归档目的地,待备库监听、网络认证和 Duplicate 完成后再配置。

3. 准备备库目录、监听和网络认证
#

3.1 设置环境并创建目录
#

在备库 oracle 用户会话中设置:

export ORACLE_SID=cmsjsyddg
export ORACLE_HOME=/u01/app/oracle/product/19.3.0/db_1
export PATH="$ORACLE_HOME/bin:$PATH"

在备库使用 root 创建目录并设置属主和权限:

install -d -o oracle -g oinstall -m 750 \
  /u01/oradata/cmsjsyddg \
  /u01/fra/cmsjsyddg \
  /u01/app/oracle/admin/cmsjsyddg/adump

OMF 负责生成文件名,目录通过以下参数指定:

参数用途
DB_CREATE_FILE_DEST/u01/oradata/cmsjsyddg数据文件创建目录
DB_CREATE_ONLINE_LOG_DEST_1/u01/oradata/cmsjsyddg控制文件与 redo member 的一个目录
DB_CREATE_ONLINE_LOG_DEST_2/u01/fra/cmsjsyddg控制文件与 redo member 的另一个目录
DB_RECOVERY_FILE_DEST/u01/fra/cmsjsyddgFRA
DB_RECOVERY_FILE_DEST_SIZE1000GFRA 配额

本方案使用 OMF 重新生成目标文件名,因此不配置 DB_FILE_NAME_CONVERTLOG_FILE_NAME_CONVERT。复制主库 SPFILE 时仍需确认没有继承旧的转换规则或额外的 DB_CREATE_ONLINE_LOG_DEST_n。两个目录若共用 /u01 文件系统,其故障隔离能力有限。Oracle 19c RMAN Duplicate 文件命名

3.2 配置静态监听
#

RMAN 会在 Duplicate 过程中重启辅助实例。静态监听使辅助实例在 NOMOUNT 阶段仍能通过 Oracle Net 连接。

在备库 Oracle Home 的 network/admin/listener.ora 中加入以下配置。如果已使用 TNS_ADMIN,应编辑它指向的文件;已有监听配置按实际情况合并。

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = cmsjsdg2)(PORT = 1521))
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = cmsjsyddg)
      (ORACLE_HOME = /u01/app/oracle/product/19.3.0/db_1)
      (SID_NAME = cmsjsyddg)
    )
  )

由备库 oracle 用户启动并检查监听;已有监听运行时使用 reload 加载配置。

lsnrctl start
lsnrctl status
lsnrctl services

本次静态服务显示:

Service "cmsjsyddg" has 1 instance(s).
Instance "cmsjsyddg", status UNKNOWN

UNKNOWN 表示监听无法通过动态注册获知实例状态,不等同于实例故障,也不能证明实例已经启动。第 4.4 节通过 SYSDBA 连接验证实例状态。

3.3 配置双向 TNS
#

备库文件为 /u01/app/oracle/product/19.3.0/db_1/network/admin/tnsnames.ora

CMSJS =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = 10.87.180.14)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = cmsjs)
    )
  )

CMSJSYDDG =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = cmsjsdg2)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = cmsjsyddg)
    )
  )

CMSJS 使用本次记录的 SCAN IP。复用时以实际 SCAN 配置为准,SERVICE_NAME 也要从主库监听服务列表核实,不能仅由 DB_NAME 推断。备库应能访问 SCAN 重定向到的 RAC 节点 VIP 和监听端口。

在两个 RAC 节点的 /u01/app/oracle/product/19.3.0/dbhome_1/network/admin/tnsnames.ora 中加入相同的 CMSJSYDDG 条目。三台主机均需能将 cmsjsdg2 解析为 10.87.201.28

在备库执行:

tnsping CMSJS
tnsping CMSJSYDDG

在两个 RAC 节点分别执行:

tnsping CMSJSYDDG

tnsping 只用于检查连接描述符和监听可达性。数据库服务、密码文件和 SYSDBA 认证还需实际登录验证。

3.4 复制密码文件
#

本次主库密码文件位于:

+DATA/CMSJS/PASSWORD/pwdcmsjs.256.1243102711

复用时先用数据库资源配置核实实际密码文件位置。由主库 grid 用户导出:

asmcmd pwcopy \
  +DATA/CMSJS/PASSWORD/pwdcmsjs.256.1243102711 \
  /tmp/orapwcmsjs

由主库 root 调整属主和权限:

chown oracle:oinstall /tmp/orapwcmsjs
chmod 600 /tmp/orapwcmsjs

再由主库 oracle 用户复制到备库:

scp /tmp/orapwcmsjs oracle@cmsjsdg2:/tmp/

确认备库目标位置没有需要保留的旧密码文件后,由 oracle 用户安装:

mv /tmp/orapwcmsjs "$ORACLE_HOME/dbs/orapwcmsjsyddg"
chmod 600 "$ORACLE_HOME/dbs/orapwcmsjsyddg"

确认后删除主库 /tmp/orapwcmsjs 中的临时副本。复制主库密码文件是为了在初始辅助实例上建立远程认证;Active Duplicate 创建物理备库时还会处理密码文件。Oracle 19c DUPLICATE 参考

4. 调整内存并启动辅助实例
#

4.1 按备库资源设置内存
#

主库每节点的 SGA_TARGET 约 353.5 GB、PGA_AGGREGATE_TARGET 约 117.8 GB、PROCESSES=8000。备库资源不足,不能直接继承这些值。

参数本次备库值
SGA_TARGET / SGA_MAX_SIZE64G / 64G
PGA_AGGREGATE_TARGET / PGA_AGGREGATE_LIMIT16G / 32G
PROCESSES / SESSIONS3000 / 4522
USE_LARGE_PAGESFALSE

这些值来自本次配置,不能作为按物理内存比例分配的通用标准。PGA target 不是硬上限,复用时还要给操作系统、恢复进程和查询负载留出空间。本次未启用 HugePages;如需启用,应结合操作系统配置另行调整。

4.2 创建 PFILE
#

在备库创建 $ORACLE_HOME/dbs/initcmsjsyddg.ora

*.db_name='cmsjs'
*.db_unique_name='cmsjsyddg'
*.compatible='19.0.0'
*.cluster_database=FALSE
*.remote_login_passwordfile='EXCLUSIVE'

*.memory_target=0
*.memory_max_target=0
*.sga_target=64G
*.sga_max_size=64G
*.pga_aggregate_target=16G
*.pga_aggregate_limit=32G
*.processes=3000
*.sessions=4522
*.use_large_pages='FALSE'

*.audit_file_dest='/u01/app/oracle/admin/cmsjsyddg/adump'
*.diagnostic_dest='/u01/app/oracle'
*.db_create_file_dest='/u01/oradata/cmsjsyddg'
*.db_create_online_log_dest_1='/u01/oradata/cmsjsyddg'
*.db_create_online_log_dest_2='/u01/fra/cmsjsyddg'
*.db_recovery_file_dest='/u01/fra/cmsjsyddg'
*.db_recovery_file_dest_size=1000G

*.standby_file_management='AUTO'
*.log_archive_config='DG_CONFIG=(cmsjs,cmsjsyddg)'
*.fal_server='CMSJS'

COMPATIBLE 应与主库实际值核对。MEMORY_TARGET=0MEMORY_MAX_TARGET=0 明确使用本文的 SGA/PGA 管理方式;主库若为 CDB,还需按第 1.3 节补充参数。

4.3 使用 PFILE 启动到 NOMOUNT
#

用 PFILE 启动辅助实例,再由 RMAN 的 SPFILE 子句复制并修改主库参数,无需提前执行 CREATE SPFILE FROM PFILE

sqlplus / as sysdba

在 SQL*Plus 中将 STARTUP 写成一行:

startup nomount pfile='/u01/app/oracle/product/19.3.0/db_1/dbs/initcmsjsyddg.ora';
show parameter spfile
select instance_name, status from v$instance;

此时 spfile 的值应为空,实例应为 cmsjsyddg / STARTED。这说明辅助实例已启动,数据库尚未挂载。

4.4 验证 SYSDBA 网络连接
#

在备库连接主库,按提示输入 SYS 密码:

sqlplus 'sys@CMSJS as sysdba'
select name, db_unique_name, database_role from v$database;

应连接到 CMSJS / cmsjs / PRIMARY

随后在备库本机及两个 RAC 节点分别测试辅助实例连接:

sqlplus 'sys@CMSJSYDDG as sysdba'
select instance_name, status from v$instance;

应连接到 cmsjsyddg / STARTED。在 NOMOUNT 阶段使用 V$INSTANCE 验证,不查询尚不可用的数据库挂载信息。

5. 执行 RMAN Active Duplicate
#

5.1 先检查将继承的主库参数
#

DUPLICATE ... SPFILE 会以主库 SPFILE 为基础生成备库参数文件。辅助实例启动用的 PFILE 不会覆盖复制后的 SPFILE,因此内存、目录和单实例配置必须在 SET 中再次指定。

主库先检查以下参数以及它们在 V$SPPARAMETER 中的 SID 作用域:

select sid, name, value
from v$spparameter
where isspecified = 'TRUE'
  and (name in (
         'db_file_name_convert', 'log_file_name_convert',
         'control_files', 'remote_listener', 'local_listener',
         'memory_target', 'memory_max_target',
         'instance_number', 'thread', 'undo_tablespace')
       or name like 'log_archive_dest%'
       or name like 'db_create_online_log_dest%')
order by name, sid;

脚本使用本次环境的 UNDOTBS1。执行前确认该 undo 表空间存在,并检查 RAC 的实例专属参数、旧路径与额外归档目的地。新 SID cmsjsyddg 不会直接匹配 cmsjs1.*cmsjs2.* 条目,应检查公共参数和新实例的实际生效值;也不要因备库为单实例而删除源 RAC 的其他 undo 数据文件。

Oracle 19c 的 RMAN SPFILE 子句支持 RESET,用于移除不再需要的参数。执行下方三个 RESET 前,要确认源 SPFILE 中存在其对应作用域的条目;未设置的参数不能机械照抄 RESET,否则可能遇到 ORA-32010。发现额外参数差异后,再针对明确的条目调整,避免批量清空 RAC 或隐藏参数。Oracle 19c DUPLICATE 参数规则ORA-32010

5.2 连接并复制
#

在备库 oracle 用户会话中启动 RMAN:

rman

分别连接并按提示输入密码:

CONNECT TARGET sys@CMSJS;
CONNECT AUXILIARY sys@CMSJSYDDG;

确认 RMAN 输出同时包含正确的 target 和 auxiliary 连接信息,再执行脚本。保留运行日志,但不要将 SYS 密码写进命令行或脚本。

RUN {
  ALLOCATE CHANNEL c1 DEVICE TYPE DISK;
  ALLOCATE CHANNEL c2 DEVICE TYPE DISK;
  ALLOCATE CHANNEL c3 DEVICE TYPE DISK;
  ALLOCATE CHANNEL c4 DEVICE TYPE DISK;

  ALLOCATE AUXILIARY CHANNEL a1 DEVICE TYPE DISK;
  ALLOCATE AUXILIARY CHANNEL a2 DEVICE TYPE DISK;
  ALLOCATE AUXILIARY CHANNEL a3 DEVICE TYPE DISK;
  ALLOCATE AUXILIARY CHANNEL a4 DEVICE TYPE DISK;

  DUPLICATE TARGET DATABASE
    FOR STANDBY
    FROM ACTIVE DATABASE
    USING COMPRESSED BACKUPSET
    DORECOVER
    SPFILE
      SET db_unique_name='cmsjsyddg'
      SET cluster_database='FALSE'
      SET cluster_database_instances='1'
      SET remote_login_passwordfile='EXCLUSIVE'
      SET memory_target='0'
      SET memory_max_target='0'
      SET sga_target='64G'
      SET sga_max_size='64G'
      SET pga_aggregate_target='16G'
      SET pga_aggregate_limit='32G'
      SET processes='3000'
      SET sessions='4522'
      SET use_large_pages='FALSE'
      SET audit_file_dest='/u01/app/oracle/admin/cmsjsyddg/adump'
      SET diagnostic_dest='/u01/app/oracle'
      SET db_create_file_dest='/u01/oradata/cmsjsyddg'
      SET db_create_online_log_dest_1='/u01/oradata/cmsjsyddg'
      SET db_create_online_log_dest_2='/u01/fra/cmsjsyddg'
      SET db_recovery_file_dest='/u01/fra/cmsjsyddg'
      SET db_recovery_file_dest_size='1000G'
      SET standby_file_management='AUTO'
      SET log_archive_config='DG_CONFIG=(cmsjs,cmsjsyddg)'
      SET log_archive_dest_1='LOCATION=USE_DB_RECOVERY_FILE_DEST VALID_FOR=(ALL_LOGFILES,ALL_ROLES) DB_UNIQUE_NAME=cmsjsyddg'
      SET log_archive_dest_state_1='ENABLE'
      SET log_archive_dest_2='SERVICE=CMSJS ASYNC NOAFFIRM VALID_FOR=(ONLINE_LOGFILE,PRIMARY_ROLE) DB_UNIQUE_NAME=cmsjs REOPEN=15'
      SET log_archive_dest_state_2='DEFER'
      SET fal_server='CMSJS'
      SET undo_tablespace='UNDOTBS1'
      RESET remote_listener
      RESET local_listener
      RESET control_files
    NOFILENAMECHECK;
}

本次使用 4 个 target channel 和 4 个 auxiliary channel。压缩备份集可减少传输数据量,同时增加 CPU 开销;通道数应结合主库负载、网络和备库写入能力调整。先用 SHOW COMPRESSION ALGORITHM 检查 RMAN 压缩算法配置,算法的许可要求不能仅凭 USING COMPRESSED BACKUPSET 推断。Oracle 19c 备份压缩

NOFILENAMECHECK 会跳过源、目标文件名冲突检查。本次主库使用 ASM,备库使用另一台主机的文件系统;复用该选项前必须确认目标不会访问或覆盖主库文件,尤其要检查共享存储和目录挂载。

5.3 检查复制结果和文件路径
#

本次运行日志以这一行结束:

Finished Duplicate Db at 2026-09-05 12:52:37

这表示本次 Duplicate 完成,持续日志应用还需后续配置。在备库执行:

select name, db_unique_name, database_role, open_mode, log_mode,
       force_logging, switchover_status
from v$database;

show parameter spfile
show parameter cluster_database
show parameter db_create_file_dest
show parameter db_recovery_file_dest
show parameter fal_server
show parameter remote_listener

select name from v$controlfile;
select file#, name from v$datafile order by file#;
select group#, type, member from v$logfile order by group#, member;
select thread#, group#, round(bytes / 1024 / 1024) mb, status
from v$standby_log order by thread#, group#;

本次 Duplicate 后为 PHYSICAL STANDBY / MOUNTEDCLUSTER_DATABASE=FALSEFAL_SERVER=CMSJSREMOTE_LISTENER 为空。文件路径记录如下,OMF 自动生成的末尾文件名以实际查询为准:

主库数据文件:+DATA/CMSJS/DATAFILE/...
备库数据文件:/u01/oradata/cmsjsyddg/CMSJSYDDG/datafile/...

控制文件副本 1:/u01/oradata/cmsjsyddg/CMSJSYDDG/controlfile/...
控制文件副本 2:/u01/fra/cmsjsyddg/CMSJSYDDG/controlfile/...

本次在线 redo 和 SRL 的 member 分布在 /u01/oradata/.../u01/fra/...。SRL 为 thread 1 的 11~14 组和 thread 2 的 15~18 组,每组 4096 MB。RMAN 按主库已有定义在备库重建这些 SRL,并非复制正在使用的 SRL 内容。实际组数、thread、容量和 member 路径均需查询确认;缺少 SRL 时先补齐,再启动持续应用。Oracle 19c DUPLICATE 的备库日志处理

6. 配置持续传输与日志应用
#

6.1 检查备库归档参数
#

复制完成后,核对下列参数是否与 Duplicate 中的备库配置一致:

alter system set log_archive_dest_1=
  'LOCATION=USE_DB_RECOVERY_FILE_DEST VALID_FOR=(ALL_LOGFILES,ALL_ROLES) DB_UNIQUE_NAME=cmsjsyddg'
  scope=both;
alter system set log_archive_dest_state_1=ENABLE scope=both;

alter system set log_archive_dest_2=
  'SERVICE=CMSJS ASYNC NOAFFIRM VALID_FOR=(ONLINE_LOGFILE,PRIMARY_ROLE) DB_UNIQUE_NAME=cmsjs REOPEN=15'
  scope=both;
alter system set log_archive_dest_state_2=ENABLE scope=both;
alter system set fal_server='CMSJS' scope=both;

DEST_1 将本地归档写入 FRA。DEST_2 仅在该数据库成为主库时发送在线 redo,所以它在物理备库角色下不会向原主库反向发送当前收到的 redo。FAL_SERVER 指向可请求缺失归档的数据库服务。

反向目的地是角色转换的准备参数,是否具备切换条件仍要经过专门检查和演练。

6.2 启动 MRP
#

在备库执行:

alter database recover managed standby database disconnect from session;

本配置具备 SRL,上述命令用于启动实时应用,无需添加 USING CURRENT LOGFILE 子句。DORECOVER 不会替代这一步持续恢复。Oracle 19c Redo Apply

6.3 启用主库向备库发送 redo
#

主库保留已验证的本地归档策略。只有本地 FRA 已配置、容量满足需求,且本次确实计划使用 FRA 归档时,才应用以下现场设置:

alter system set log_archive_dest_1=
  'LOCATION=USE_DB_RECOVERY_FILE_DEST VALID_FOR=(ALL_LOGFILES,ALL_ROLES) DB_UNIQUE_NAME=cmsjs'
  scope=both sid='*';

确认 DEST_2 未用于其他目的地后,在主库执行:

alter system set log_archive_dest_2=
  'SERVICE=CMSJSYDDG ASYNC NOAFFIRM VALID_FOR=(ONLINE_LOGFILE,PRIMARY_ROLE) DB_UNIQUE_NAME=cmsjsyddg REOPEN=15'
  scope=both sid='*';
alter system set log_archive_dest_state_2=ENABLE scope=both sid='*';
alter system set fal_server='CMSJSYDDG' scope=both sid='*';

SID='*' 写入 RAC 的公共设置。若已有实例专属值,还要核对两个实例实际生效的参数:

select inst_id, name, value
from gv$parameter
where name in ('log_archive_config', 'log_archive_dest_2',
               'log_archive_dest_state_2', 'fal_server')
order by name, inst_id;

select protection_mode, protection_level from v$database;

本次采用 ASYNC NOAFFIRM,主库原有保护模式为 MAXIMUM PERFORMANCE。ASYNC 不等待备库接收确认后才完成主库提交,因此不能据此承诺故障时零数据丢失;配置传输属性后也应查询实际保护模式。Oracle 19c 保护模式

7. 打开 Active Data Guard
#

先确认备库已开始接收 redo,恢复进程存在且没有阻塞恢复的错误。需同时提供只读查询并已确认相应许可时,在备库依次执行:

alter database recover managed standby database cancel;
alter database open read only;
alter database recover managed standby database disconnect from session;

select name, db_unique_name, database_role, open_mode from v$database;

本次结果:

NAME              CMSJS
DB_UNIQUE_NAME    cmsjsyddg
DATABASE_ROLE     PHYSICAL STANDBY
OPEN_MODE         READ ONLY WITH APPLY

READ ONLY WITH APPLY 表明数据库已只读打开且运行日志应用。还需按第 8 节检查两个 thread 的最新 redo 是否已传输和应用。

8. 验证两个 thread 的传输与应用
#

8.1 从两个主库实例检查远端目的地
#

在主库执行:

set lines 300 pages 100
col destination for a25
col error for a80
col database_mode for a20
col recovery_mode for a40

select inst_id, dest_id, status, database_mode, recovery_mode,
       destination, error
from gv$archive_dest_status
where dest_id = 2
order by inst_id;

本次打开 ADG 后的记录为:

INST_ID  STATUS  DATABASE_MODE   RECOVERY_MODE
1        VALID   OPEN_READ-ONLY  MANAGED REAL TIME APPLY WITH QUERY
2        VALID   OPEN_READ-ONLY  MANAGED REAL TIME APPLY WITH QUERY

两个实例的 ERROR 均为空。OPEN_READ-ONLYWITH QUERY 对应第 7 节打开 ADG 后的状态,在只挂载备库时不应要求这两个结果。

8.2 产生可追踪的日志进展
#

sjdb1 本地连接 cmsjs1,再在 sjdb2 本地连接 cmsjs2,分别执行:

select instance_name, thread# from v$instance;
alter system switch logfile;

分别使用各实例的本地会话,确认两个 thread 都有日志推进。ALTER SYSTEM ARCHIVE LOG CURRENT 未指定 thread 时可作用于所有启用的 thread,不能用它来表示只切换当前节点的 thread。Oracle 19c ALTER SYSTEM

在备库观察 SRL:

select thread#, group#, sequence#, status, archived
from v$standby_log
order by thread#, group#;

本次记录过以下状态:

THREAD#  GROUP#  SEQUENCE#  STATUS
1        11      160        ACTIVE
2        15      50         ACTIVE

这些组号和 sequence 是现场快照,不是复用时必须出现的固定值。检查重点是两个 thread 都能接收 redo,并在主库产生日志后持续推进。

8.3 检查 RFS 和 MRP
#

Oracle 19c 推荐使用 V$DATAGUARD_PROCESS 查看 Data Guard 进程:

select name, role, action, thread#, sequence#
from v$dataguard_process
where name in ('RFS', 'MRP0')
order by name, thread#;

也可使用以下兼容查询,与现场输出对照:

select process, status, thread#, sequence#
from v$managed_standby
where process in ('RFS', 'MRP0')
order by process, thread#;

V$MANAGED_STANDBY 已被弃用,但在 19c 仍可用于读取已有巡检结果。MRP 的 APPLYING_LOG 表示正在应用;WAIT_FOR_LOG 表示等待日志,需要与时间戳、归档缺口和传输状态一起解释。单实例备库不要求每个 thread 各有一个 MRP0Oracle 19c V$DATAGUARD_PROCESS

8.4 检查延迟和指标是否更新
#

在备库执行:

set lines 250 pages 100
col name for a20
col value for a25
col time_computed for a25
col datum_time for a25

select name, value, time_computed, datum_time
from v$dataguard_stats
where name in ('transport lag', 'apply lag');

本次记录的延迟值为:

transport lag   +00 00:00:00
apply lag       +00 00:00:00

这表示该次指标采样未显示传输或应用积压,不能解释为网络传输没有耗时。TIME_COMPUTED 是指标计算时间,DATUM_TIME 是计算所依据的主库数据到达备库的时间。应间隔查询并结合主库日志进展观察;若 DATUM_TIME 一直不变,即使值为 0,也不能据此判定同步正常。Oracle 19c V$DATAGUARD_STATS

8.5 区分归档序号差与恢复缺口
#

下列查询只比较当前 resetlogs 分支中,RFS 记录的最大接收序号与 APPLIED=YES 的最大序号:

select thread#,
       max(sequence#) last_received,
       max(case when applied = 'YES' then sequence# end) last_applied,
       max(sequence#) -
       max(case when applied = 'YES' then sequence# end) archived_seq_delta
from v$archived_log
where registrar = 'RFS'
  and resetlogs_change# = (select resetlogs_change# from v$database)
group by thread#
order by thread#;

实时应用可能正在读取尚未归档的 SRL,因此这个差值不能直接换算成应用延迟。APPLIED=IN-MEMORY 也不等于数据文件已完成更新;以最大序号相减还会漏掉中间缺失的序号,或在没有已应用记录时返回空值。Oracle 19c V$ARCHIVED_LOG

另查当前阻塞恢复的归档缺口:

select thread#, low_sequence#, high_sequence# from v$archive_gap;

发现缺口时,处理后重复查询,直到当前阻塞缺口消除。查询为空时,仍需结合两个 thread 的进展、进程状态和持续更新的延迟指标判断同步状态。Oracle 19c V$ARCHIVE_GAP

8.6 记录验收基线
#

检查位置检查项本次记录或验收要求
主库数据库角色与打开模式PRIMARY / READ WRITE
主库RAC 实例与 threadcmsjs1 / 1cmsjs2 / 2
主库保护模式MAXIMUM PERFORMANCE
主库两实例 DEST_2VALIDERROR 为空
备库角色与打开模式PHYSICAL STANDBY / READ ONLY WITH APPLY
备库集群参数CLUSTER_DATABASE=FALSE
备库SRL每个 thread 4 组,每组 4096 MB
备库redo 进展两个 thread 都能接收并持续推进
备库应用进程MRP 存在,无持续阻塞恢复的错误
备库延迟本次值为 0;复验还需确认 DATUM_TIME 更新
备库归档缺口复验确认无当前阻塞恢复的缺口

现场记录给出了最终状态快照,但缺少多次 DATUM_TIME 采样和完整缺口查询结果。交付验收时需补充并保存这些结果,以确认日志传输持续正常。

9. 备库归档清理与自动化
#

9.1 定义清理范围
#

本次清理条件为:归档由 RFS 接收、APPLIED='YES'、归档完成时间早于当前时间 7 天。七天按 COMPLETION_TIME 计算,不按 redo 开始时间计算;不要把它直接改写成含义不同的 RMAN UNTIL TIME

下方脚本为 v1.1。除上述条件外,脚本还要求记录为可用、未删除;同一路径若存在不满足条件的活动记录,则跳过该文件。APPLIED='IN-MEMORY' 不进入删除清单。Oracle 19c V$ARCHIVED_LOG

脚本按精确文件名生成 DELETE ARCHIVELOG,不用 LIKE。即使传入完整文件路径,LIKE 中的 _% 仍可能扩大匹配范围。SQL 查询失败、输出异常或数据库身份不符时,脚本直接退出。Oracle 19c recordSpec

执行前在备库 RMAN 会话中核对现有策略:

SHOW ARCHIVELOG DELETION POLICY;
SHOW RETENTION POLICY;

七天只是这个脚本的筛选门槛,不是 FRA 至少保留七天归档的保证。FRA 仍可能回收符合条件的文件,恢复窗口也不等于每个文件的保留天数。备份、Flashback 和级联备库有额外需要时,应先确定整体策略。脚本不使用 FORCE 绕过 RMAN 的删除策略。Oracle 19c RMAN 配置

9.2 安装脚本并先预览
#

在备库以 oracle 用户创建目录,将下面内容保存为 /home/oracle/scripts/clean_arch_cmsjsyddg.sh,使用 LF 换行:

mkdir -p /home/oracle/scripts /home/oracle/log
#!/bin/bash
# 默认只预览;核对候选清单后,使用 --delete 执行删除。
set -Eeuo pipefail
umask 077
SCRIPT_VERSION=1.1

MODE=${1:---preview}
case "$MODE" in
    --preview|--delete) ;;
    *) printf 'Usage: %s [--preview|--delete]\n' "$0" >&2; exit 2 ;;
esac

export ORACLE_SID=cmsjsyddg
export ORACLE_HOME=/u01/app/oracle/product/19.3.0/db_1
export PATH="$ORACLE_HOME/bin:/usr/bin:/bin"
export NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'
unset TWO_TASK ORACLE_PDB_SID

LOG_DIR=/home/oracle/log
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/clean_arch_${ORACLE_SID}_$(date +%Y%m%d).log"
exec >>"$LOG_FILE" 2>&1

# 防止同一脚本重叠运行;此锁不会阻止数据库角色切换。
exec 9>"$LOG_DIR/.clean_arch_${ORACLE_SID}.lock"
if ! flock -n 9; then
    printf '%s Another cleanup is running.\n' "$(date '+%F %T')"
    exit 75
fi

TMP_DIR=$(mktemp -d "$LOG_DIR/.clean_arch_${ORACLE_SID}.XXXXXX")
cleanup() {
    rc=$?
    trap - EXIT
    rm -f -- "$TMP_DIR/role.log" "$TMP_DIR/candidates.rman" "$TMP_DIR/rman.log"
    rmdir -- "$TMP_DIR"
    printf '%s Cleanup ended; exit=%s\n' "$(date '+%F %T')" "$rc"
    exit "$rc"
}
trap cleanup EXIT
printf '\n%s Cleanup started; version=%s; mode=%s\n' "$(date '+%F %T')" "$SCRIPT_VERSION" "$MODE"

check_identity() {
    if ! sqlplus -L -s / as sysdba >"$TMP_DIR/role.log" 2>&1 <<'SQL'
whenever oserror exit 2
whenever sqlerror exit 1
set heading off feedback off pagesize 0 verify off echo off
select case
         when database_role = 'PHYSICAL STANDBY'
          and lower(db_unique_name) = 'cmsjsyddg'
         then 'IDENTITY_OK'
         else 'IDENTITY_MISMATCH'
       end
from v$database;
exit success
SQL
    then
        cat "$TMP_DIR/role.log"
        return 1
    fi
    # 只接受预期输出,SQL*Plus 自身的 SP2 错误也不会被当成成功。
    if [[ "$(sed '/^[[:space:]]*$/d; s/^[[:space:]]*//; s/[[:space:]]*$//' "$TMP_DIR/role.log")" != 'IDENTITY_OK' ]]; then
        cat "$TMP_DIR/role.log"
        printf 'Database identity or role check failed.\n'
        return 1
    fi
}

check_identity

# 只生成完整文件名,不使用 LIKE;下划线和百分号不会扩大匹配范围。
# 同名活动记录若不满足条件,则跳过该文件;说明留在 Shell 层。
if ! sqlplus -L -s / as sysdba >"$TMP_DIR/candidates.rman" 2>&1 <<'SQL'
whenever oserror exit 2
whenever sqlerror exit 1
set heading off feedback off pagesize 0 verify off echo off
set linesize 32767 trimout on trimspool on tab off define off
select distinct 'delete noprompt archivelog ''' || a.name || ''';'
from v$archived_log a
where a.registrar = 'RFS'
  and a.applied = 'YES'
  and a.archived = 'YES'
  and a.deleted = 'NO'
  and a.status = 'A'
  and a.name is not null
  and a.completion_time < sysdate - 7
  and not exists (
      select 1
      from v$archived_log b
      where b.name = a.name
        and b.deleted = 'NO'
        and b.status = 'A'
        and (nvl(b.registrar, '?') <> 'RFS'
          or nvl(b.applied, '?') <> 'YES'
          or nvl(b.archived, '?') <> 'YES'
          or b.completion_time is null
          or b.completion_time >= sysdate - 7)
  )
order by 1;
exit success
SQL
then
    cat "$TMP_DIR/candidates.rman"
    exit 1
fi

# 拒绝诊断文本、换行文件名和含单引号的文件名,避免生成异常 RMAN 命令。
if grep -Ev "^(delete noprompt archivelog '[^']+';|[[:space:]]*)$" "$TMP_DIR/candidates.rman"; then
    printf 'Unexpected candidate output; no RMAN deletion was started.\n'
    exit 1
fi
ARCH_COUNT=$(grep -c '^delete noprompt archivelog ' "$TMP_DIR/candidates.rman" || true)
printf 'Eligible file names: %s\n' "$ARCH_COUNT"
cat "$TMP_DIR/candidates.rman"

# 只轮转脚本日志,不操作归档目录。
find "$LOG_DIR" -maxdepth 1 -type f \
    -name "clean_arch_${ORACLE_SID}_*.log" -mtime +90 -delete

if [[ "$MODE" == '--preview' || "$ARCH_COUNT" -eq 0 ]]; then
    printf 'Preview completed, or no eligible file was found.\n'
    exit 0
fi

# 缩小检查与删除之间的窗口;不能替代切换期间暂停调度。
check_identity
if rman target / cmdfile="$TMP_DIR/candidates.rman" >"$TMP_DIR/rman.log" 2>&1; then
    RMAN_RC=0
else
    RMAN_RC=$?
fi
cat "$TMP_DIR/rman.log"
if [[ "$RMAN_RC" -ne 0 ]]; then
    exit "$RMAN_RC"
fi

# RMAN 可能跳过受删除策略保护的文件;发现诊断码时要求检查日志。
if grep -Eq '(RMAN-|ORA-)[0-9]{5}' "$TMP_DIR/rman.log"; then
    printf 'Oracle/RMAN diagnostic messages were found; inspect the log.\n'
    exit 3
fi

printf 'RMAN command file completed. Check its output for actual deleted files.\n'

该脚本使用 Bash、flockmktempflock 只防止脚本自身重叠运行,不能阻止数据库角色转换。切换、故障转移、重建或控制文件维护期间,应先暂停调度,等正在运行的作业退出;两次角色检查仍不能消除检查与删除之间的时间窗口。

脚本不自动执行全库 CROSSCHECK ARCHIVELOG ALLDELETE EXPIREDEXPIRED 表示 RMAN 检查时文件缺失或不可访问,不表示超过七天;相关元数据维护应独立检查并记录结果。Oracle 19c DELETE

授权、检查语法并预览:

chmod 750 /home/oracle/scripts/clean_arch_cmsjsyddg.sh
bash -n /home/oracle/scripts/clean_arch_cmsjsyddg.sh
/home/oracle/scripts/clean_arch_cmsjsyddg.sh --preview
cat /home/oracle/log/clean_arch_cmsjsyddg_$(date +%Y%m%d).log

2026-09-05 13:50:56,在 cmsjsdg2 上由 oracle 用户执行 v1.1 的 --preview,日志显示 Eligible file names: 0Cleanup ended; exit=0,Shell 返回码也为 0。这次现场运行通过了身份检查和候选查询,没有符合条件的归档,未调用 RMAN 删除。它验证了预览和空候选处理,尚未验证存在候选文件时的实际删除。

预览不删除数据库归档,但会记录候选清单并清理超过 90 天的本脚本执行日志。确认文件名、应用状态、时间范围及 RMAN 策略符合要求后,手工执行一次正式清理:

/home/oracle/scripts/clean_arch_cmsjsyddg.sh --delete

检查退出码和日志中的实际删除结果,再复查第 8 节的同步状态与第 10.4 节的 FRA 使用量。候选文件数不一定等于删除数,RMAN 可能因策略保护跳过部分文件;脚本发现诊断码时会以非零值退出,便于人工确认。

9.3 配置定时任务
#

完成手工验证后,以备库 oracle 用户编辑 crontab

crontab -e

每天服务器本地时间 03:30 执行正式清理:

30 3 * * * /home/oracle/scripts/clean_arch_cmsjsyddg.sh --delete

crontab -l 检查调度。脚本将运行信息写入 /home/oracle/log,日志保留 90 天;将非零退出和日志中的失败信息接入现有监控。本文没有配置告警通知服务。