上个月给某套核心业务库搭建Data Guard,考虑到生产环境不能停,我选了“备份恢复”这条最稳妥的路线。原以为就是常规的RMAN备份、传到备机、恢复控制文件、起MRP,结果一晚上接连踩了路径不映射、参数没对齐、归档链路断档、密码文件不一致好几个坑,前前后后折腾到后半夜才把主备同步拉到正常。这篇就把这次用备份恢复创建Data Guard的问题记录整理成文,每一步报错、定位过程、最终解法都按当时现场的逻辑梳理出来。如果你也正准备用RMAN备份方式给生产库搭物理备库,或者搭完之后MRP一直不干活、v$archive_gap冒出来都查不出来,这篇应该能帮你少走不少弯路。
1. 为什么选备份恢复这条线:适用场景与整体链路
1.1 备份恢复 vs duplicate 怎么选
创建Data Guard方案时,在线的传统做法是直接用RMAN duplicate,它支持FROM ACTIVE DATABASE,不需要停库,从主库在线拷贝数据文件到备库,一步到位。遇到网络带宽充足、主库压力可控的场景,duplicate确实香,尤其11g以后duplicate会自动处理很多文件路径和状态问题。可我在现场不太敢一上来就赌duplicate,原因有三个:一是主库数据量不小,在线复制过程中主库日志切换和生产IO会有明显叠加;二是duplicate出错时排查链路长,特别是RMAN自动生成的恢复脚本黑盒感强,有些日志只有到报错时才能看到;三是这套系统的重要程度决定了我必须用最“笨”但最可控的方式——RMAN全量备份加归档备份,先把生产库的快照完整落到备机,再手工控制恢复节奏。
备份恢复这条线的核心思路是:主库在备份窗口内做全量备份,连同备份后的归档日志一起送到备机,备机上恢复参数文件、控制文件,再restore数据文件并应用归档到一致状态,最后配置DG参数和日志传输链路。整个过程主库正常对外服务,备份期间的新变化全部由归档承载。
1.2 全链路步骤和前置条件
实际操作顺序大概是:
- 主库开FORCE LOGGING,避免后续有NOLOGGING操作造成备库数据空洞。
- 确认归档模式开启,归档目录有足够空间。
- RMAN备份:全量数据库加归档日志备份,尽量在业务低峰执行。
- 备机安装相同版本数据库软件,建好目录,准备参数文件。
- 将备份集传送到备机,恢复SPFILE、控制文件(用NO MOUNT方式),设置备库参数。
- 在备库做RMAN RESTORE DATABASE,恢复数据文件。
- 配置监听、tnsnames、密码文件。
- 备库创建STANDBY LOGFILE,启动MRP进程。
- 验证归档传输和应用延迟。
前置条件里最容易遗漏的是密码文件。DG主备之间的日志传输和FAL进程都要通过SYS用户建立远程会话,备库密码文件里的SYS密码必须和主库一致。这个我在问题四里栽过一次,后面专门讲。
1.3 这条路线最容易出问题的三个环节
我这次踩坑集中在三个环节。第一是文件路径映射:主备库目录结构不一致时,RMAN恢复出来的数据文件路径对不上,备库MOUNT后一打开就报ORA-01157/01110,这是新手第一个必踩的坑。第二是参数文件恢复后没有针对备库角色做修正:很多人直接把主库SPFILE恢复到备库,db_unique_name没改、FAL相关参数缺失,结果备库虽然能起来,MRP进程却一直不干活。第三是归档传输链路:备份窗口和DG配置窗口之间产生的归档如果没送到备库,MRP一启动就报GAP,接着又可能因为监听、tnsnames、密码文件对不上导致FAL会话建不起来。这三个环节下面逐一还原。
2. 备库起不来的第一道坎:文件路径映射与目录规划
2.1 报错现场:ORA-01157/ORA-01110
那天的第一波报错出现在备库RMAN恢复完数据文件、执行ALTER DATABASE OPEN的时候。备库日志里干干净净两行:
ORA-01157: cannot identify/lock data file 1 - see DBWR trace file ORA-01110: data file 1: '/u01/app/oracle/oradata/PROD/system01.dbf'
这说的是系统表空间文件在备机上根本找不到。主库的数据文件放在/u01/app/oracle/oradata/PROD下,而备机当时磁盘规划是/u02/app/oracle/oradata/STANDBY,底层物理磁盘都在/u02上。RMAN恢复时不管你备库长什么样,直接把备份里记录的路径原封不动restore出来。备机没有/u01这个挂载点,DBWR进程锁文件自然锁不上。
当时第一反应是查RMAN的SET NEWNAME有没有生效,一查发现我根本没写。RMAN restore database在目标库控制文件里记录的路径不存在时,最优解是用SET NEWNAME配合DB_FILE_NAME_CONVERT这类参数去做路径映射,而不是手动猜。
2.2 主备目录不一致时的转换原理
DG体系里,主备库路径可以不同,但数据库要知道怎么把主库的文件路径翻译成备库的路径。翻译动作有两层:一层交给RMAN,在恢复阶段用SET NEWNAME指定备库实际目录;另一层交给数据库本身,通过DB_FILE_NAME_CONVERT和LOG_FILE_NAME_CONVERT两个参数,在备库应用日志、添加数据文件、创建在线日志时自动完成路径替换。
转换参数的格式就是成对的字符串替换:DB_FILE_NAME_CONVERT='主库路径','备库路径'。数据库拿到主库传来的文件路径时,把左边字符串替换成右边。需要注意它做的是前缀或全路径字符串替换,不是目录拼接。比如主库路径含PROD,备库想换成STANDBY,就得写成DB_FILE_NAME_CONVERT='/oradata/PROD','/oradata/STANDBY'。日志文件同理,LOG_FILE_NAME_CONVERT负责在线日志和standby日志的路径映射。
顺带多说一句,OMF环境下文件路径是Oracle自动管理的,DB_CREATE_FILE_DEST这类参数决定文件落点,路径转换场景相对少。但传统非OMF环境,特别是老库,几乎都靠这两个参数硬映射,配错一个后面所有新加的数据文件都会掉链子。
2.3 转换参数的配置与验证
我的备库SPFILE是从主库备份恢复出来的,路径相关参数还是主库的值,所以恢复完成后第一件事是动态改掉:
ALTER SYSTEM SET DB_FILE_NAME_CONVERT='/u01/app/oracle/oradata/PROD','/u02/app/oracle/oradata/STANDBY' SCOPE=BOTH; ALTER SYSTEM SET LOG_FILE_NAME_CONVERT='/u01/app/oracle/oradata/PROD','/u02/app/oracle/oradata/STANDBY' SCOPE=BOTH;
注意SCOPE=BOTH,因为数据库已经用SPFILE启动,同时要写进SPFILE,否则重启又丢。改完参数后,RMAN端还要重新指定一遍恢复路径,跟参数互相配合:
RUN { SET NEWNAME FOR DATABASE TO '/u02/app/oracle/oradata/STANDBY/%U'; RESTORE DATABASE; SWITCH DATAFILE ALL; }
这段脚本里SET NEWNAME把每个数据文件统一放到/u02,SWITCH DATAFILE ALL让控制文件指向新路径。没有SWITCH这一步,控制文件记录的还是主库老路径,后面照样报ORA-01157。
验证就简单了:查v$datafile,看每行文件的NAME列是不是已经落在备库实际目录下。再查v$logfile,确认在线日志路径也换过来了。当时我把v$datafile的输出扫了一遍,确认没有一条指向/u01,心才放下一半。
2.4 备库目录规划建议
经历过这次,我对备库目录规划有了几个固定习惯:备库文件系统布局最好一开始就和主库保持同一套命名风格,即使挂载点不同,也要保证db_file_name_convert写起来是一条简单的字符串替换。比如主库/u01/oradata,备库/backup/oradata,两个路径都包含oradata,替换规则清晰,不容易出错。
另外,数据文件、控制文件、在线日志、归档日志、standby log的目录要分开。归档目录和standby log目录如果跟数据文件挤在同一块盘,备库压力上来时IO互相干扰,特别是MRP应用和远程归档同时争抢,容易让延迟越拉越大。我后来在备机上把归档目录独立挂了一块盘,再把standby log放到单独目录,应用延迟稳定了很多。
3. MRP进程不干活:参数文件、db_unique_name和FAL配置的连环坑
3.1 现象:MRP0停滞在not started
文件路径问题解决后,备库成功MOUNT并打开到READ ONLY状态,我本以为马上就能起MRP。结果执行:
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION;
语句正常返回,但备库始终没有进入应用日志的状态。查v$managed_standby,MRP0进程根本不在列表里;查v$dataguard_status,只看到一条“MRP0 not started”之类的记录,没有任何错误码提示。这比报错更烦人:数据库层面几乎什么都没说,只能从参数和日志两头反推。
3.2 定位方法:按日志逐层排查
当时我把排查拆成三条线:备库alert日志、主备两边的DG相关参数、连接链路可不可达。备库alert日志刷了几屏只看到RFS进程在接收文件,但MRP始终没被触发。这时我意识到问题大概率出在参数配置上——MRP启动时会先校验备库角色和DG身份,校验不过就静默放弃启动。
先看两个最关键的参数:
SHOW PARAMETER DB_UNIQUE_NAME SHOW PARAMETER FAL_SERVER SHOW PARAMETER LOG_ARCHIVE_CONFIG
一查就发现问题了:db_unique_name还是主库的名字PROD,而不是备库的STANDBY。FAL_SERVER和FAL_CLIENT压根没设置,LOG_ARCHIVE_CONFIG也没有指定DG_CONFIG列表。等于备库完全不知道自己是备库,也不知道该找谁要日志,MRP自然无从启动。
3.3 三处参数错误逐个击破
第一处是db_unique_name。DG配置里主备库的db_unique_name必须不同,主库是PROD,备库就该是STANDBY。我当时是从主库备份恢复的SPFILE,恢复后没改这个名字,导致备库认为自己是主库角色,数据保护配置全部指向本机。修改方法:
ALTER SYSTEM SET DB_UNIQUE_NAME='STANDBY' SCOPE=SPFILE;
改完必须重启实例才能生效,因为db_unique_name属于静态参数。我记得当时为了快,先SHUTDOWN IMMEDIATE再STARTUP MOUNT,确认新名字生效后才继续。
第二处是LOG_ARCHIVE_CONFIG。这个参数定义DG配置内允许传输日志的目标和来源,DGID一定要两边写同一个名字。主备库我都设置成:
LOG_ARCHIVE_CONFIG='DG_CONFIG=(PROD,STANDBY)'
主库在PROD上,备库在STANDBY上,两边都识别同一个DG集合。如果这里忘了,备库RFS收到主库日志时会因为DGID不匹配直接拒绝,主库那边报ORA-16047,措辞是DGID mismatch between destination and source database。
第三处是FAL参数。FAL全称Fetch Archived Log,备库发现归档有GAP时,通过FAL_SERVER指定的源去拉缺失日志。备库上至少要配FAL_SERVER指向主库的tnsnames别名,主库双向一般不会太严格,但稳妥起见主备都写上:
FAL_SERVER='PROD' FAL_CLIENT='STANDBY'
这里的PROD和STANDBY是TNSNAMES里的别名,不是db_unique_name。配完FAL还要顺手确认两件事:主库LOG_ARCHIVE_DEST_2指向备库的目标设置正确,备库本地有LOG_ARCHIVE_DEST_1保证自己归档路径可用。
3.4 重启MRP的完整命令
参数全部改完,重启备库实例到MOUNT状态后,我按顺序执行:
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION;
这次v$managed_standby里终于出现了MRP0进程,状态是WAIT_FOR_LOG,意思是正在等主库传日志。备库状态也从READ ONLY变成READ ONLY WITH APPLY,数据保护模式显示为MAXIMUM PERFORMANCE。之后从主库切换一次日志,备库那边立即就能看到新归档被应用,延迟从“未知”变成几秒,这条线才算真正通了。
4. 归档链路断档:GAP问题与FAL会话建立失败的完整排查
4.1 备份窗口期落下的归档日志
MRP进程起来后,我以为万事大吉,结果过了半小时一看v$archived_log,备库的归档序列号和主库差了二十几个。查v$archive_gap时果然出数据了:
THREAD# LOW_SEQUENCE# HIGH_SEQUENCE# 1 123 146
这二十多个归档就是备份完成之后、DG参数配置完成之前这个窗口期在主库生成的日志。主库虽然从配置完成后开始实时传日志,但历史缺口不会自动补,需要一个触发机制把缺口拉过来。DG里这个活主要由FAL进程干,备库发现自己有gap时向FAL_SERVER请求对应序列的归档。
4.2 FAL无法连接备库:tnsnames与静态监听的坑
理论上FAL会自动把缺口拉平,但我等了十分钟,v$archive_gap纹丝不动。查备库alert日志,看到连接主库的尝试反复失败,错误是ORA-12541: TNS:no listener。第一次看到这个错我还以为备库监听没起,再三确认监听是LISTENER=ONLINE。后来才反应过来,主库要去连接备库的RFS进程,但备库监听器配置的是动态注册,而实例处于MOUNT状态下动态注册不会发生,监听器上根本没有备库的服务名,主库自然连不上。
解决办法是给备库监听器加静态注册,在listener.ora里手写SID_DESC,指向备库实例名和Oracle路径:
SID_LIST_LISTENER_STANDBY = (SID_LIST = (SID_DESC = (GLOBAL_DBNAME = STANDBY_DGMGRL) (ORACLE_HOME = /u02/app/oracle/product/11.2.0/dbhome_1) (SID_NAME = STANDBY)))
GLOBAL_DBNAME这里我写的是备库的db_unique_name加后缀,主要用于DG Broker场景;实际日志传输走的主要是SID_NAME。写完reload监听,再测主库到备库的连接:
tnsping STANDBY sqlplus sys/sys@STANDBY as sysdba
能登进去,说明静态注册生效了。这一点经验后来成了我检查清单里的常驻项:DG环境里备库只要处于MOUNT状态,就别指望动态注册,静态注册必须预先配好。
4.3 密码文件不一致导致的ORA-01031
监听通了之后,我以为GAP马上能补上,结果备库日志又开始刷ORA-01031: insufficient privileges。这个坑更隐蔽:主库的SYS密码文件和备库的SYS密码不一致。主库之前因为安全加固改过SYS密码,orapwd文件随之变化;备库上的orapwSTANDBY文件还是当初建库时生成的,里面记录的验证口令和主库完全对不上。DG日志传输无论走LGWR还是ARCH进程,都要以SYS身份在两端建立远程会话,口令不一致自然被拒。
处理方式简单粗暴但有效:在备机上用与主库相同的SYS密码重新生成密码文件。
orapwd file=$ORACLE_HOME/dbs/orapwSTANDBY password=与主库一致的密码 entries=10 force=y
生成后重启监听,再手动切换一次主库日志,备库的ORA-01031立刻消失。这个坑提醒我两件事:第一,密码文件是DG链路的“握手凭据”,任何一端改了SYS密码,另一端必须同步改;第二,遇到ORA-01031别急着怀疑权限配置,先看两端orapwd文件的修改时间和口令状态。
4.4 手工补GAP后恢复同步
FAL会话正常后,理论上缺口会自动补齐,但我习惯手工先补一把,原因是备份窗口涉及大量归档,等FAL一条条拉太慢。手工方式是把主库缺失序列对应的归档拷贝到备库,然后在备库RMAN里catalog:
CATALOG START WITH '/backup/archive/STANDBY/';
REGISTER这些归档后,MRP进程会自动应用。如果备库有多个线程,还需要对每个THREAD#都检查gap。补完之后我再查v$archive_gap为空,v$dataguard_stats里APPLY_LAG从几十分钟降到了0,主备日志序列号完全对齐,这一步才算彻底走完。
5. 两个“平时不疼、切换要命”的隐患:standby redo log与force logging
5.1 standby redo log:创建时机与大小设计
归档链路补齐后,主备同步已经正常,但我在做切换预演前又发现两个隐患,第一个是备库压根没建standby redo log。很多人以为备库只要接收并应用归档就行,standby log可以后补——理论上确实能跑,但遇到无主库实时传输或故障切换时,它就是生死线。主库的日志先传到备库的standby redo log,MRP才能实时应用;没有standby log,备库只能每来一个归档就应用一个,切换瞬间很可能会丢日志。
创建时机必须是MRP进程启动之前,或至少是备库还在MOUNT且没有应用日志时。我当时把MRP先停掉,然后按组创建:
ALTER DATABASE ADD STANDBY LOGFILE GROUP 10 ('/u02/app/oracle/oradata/STANDBY/stdby10.log') SIZE 200M; ALTER DATABASE ADD STANDBY LOGFILE GROUP 11 ('/u02/app/oracle/oradata/STANDBY/stdby11.log') SIZE 200M; ALTER DATABASE ADD STANDBY LOGFILE GROUP 12 ('/u02/app/oracle/oradata/STANDBY/stdby12.log') SIZE 200M; ALTER DATABASE ADD STANDBY LOGFILE GROUP 13 ('/u02/app/oracle/oradata/STANDBY/stdby13.log') SIZE 200M;
组数原则是比主库在线日志组数多一组,每组大小等于或略大于主库日志组大小。主库是3组200M,我就建了4组200M,保证备库在接收日志时有足够的缓冲组轮转,不会因为日志同步速度跟不上而阻塞。
5.2 force logging没开的代价
第二个隐患是主库FORCE LOGGING为NO。这个问题平时不痛不痒,但一旦主库有直接路径加载、NOLOGGING建索引之类的操作,数据块的内容不会完整写入日志。备库通过归档应用时拿到的是数据块“空洞”,要么MRP进程停滞报ORA-26040,要么干脆把数据文件应用成不可用状态,切换上去直接丢数据或者起不来。
FORCE LOGGING的作用就是强制整个数据库所有操作都写日志,不管语句里写了NOLOGGING还是UNRECOVERABLE,统一按完整日志处理。我当时在主库执行:
SELECT FORCE_LOGGING FROM V$DATABASE;
结果返回NO,立刻补上:
ALTER DATABASE FORCE LOGGING;
顺带说一句,这个操作要在主库做,而且要确认已经记录进数据字典。修改完再查,FORCE_LOGGING变成YES。对于DG环境,我建议搭建初期就开FORCE LOGGING,而不是等业务跑起来发现有NOLOGING操作再回头补。
5.3 隐患检查与补救操作
这两个隐患的共同特点是:不影响日常同步,只影响故障切换。V$DATABASE里OPEN_MODE显示READ ONLY WITH APPLY、延迟为0,不代表切换安全。我在切换预演前加了几条固定检查:一查FORCE_LOGGING是否为YES,二查standby logfile的组数和大小,三查v$standby_log里有没有内容写入过。这些检查后来都进了我维护DG的标准清单。任何DG环境,不管是不是我亲手搭的,进入维护期第一件事就是过一遍这几个状态,省得业务真的切换时被反杀。
6. 把踩坑经验固化成检查清单
6.1 数据库与进程状态检查
这次搭建过程的每一步几乎都被某个坑卡了一下,事后我把所有踩过的点整理成了一份巡检清单,现在每次做DG巡检和切换预演都直接照做。第一步检查数据库角色和进程:
- 查v$database的DATABASE_ROLE,备库必须是PHYSICAL STANDBY,不能还是PRIMARY。
- 查OPEN_MODE,正常备库应该是READ ONLY WITH APPLY;如果只是READ ONLY说明MRP没运行。
- 查v$managed_standby,MRP0状态应为WAIT_FOR_LOG,不能是NOT STARTED;RFS进程要存在,数量要合理。
- 查v$dataguard_stats,APPLY_LAG和TRANSPORT_LAG都应该接近0,最好小于一分钟。
- 查v$archive_gap,结果为空才能说明日志链路没有缺口。
这套命令我在备库上执行一遍只要几秒钟,但能覆盖绝大部分“备库看起来正常、实际不干活”的场景。
6.2 数据保护与日志链路检查
第二步检查数据保护配置和日志链路。首先要确认FORCE_LOGGING是YES,其次看LOG_ARCHIVE_DEST_2在主库上的配置是否指向备库,参数里VALID_FOR=(ONLINE_LOGFILES,PRIMARY_ROLE)这类条件是否写对。接着检查db_unique_name、FAL_SERVER、FAL_CLIENT、LOG_ARCHIVE_CONFIG两边是否完全对齐,特别是DG_CONFIG里的库名跟db_unique_name要一致。
再检查文件名转换参数是否覆盖了数据文件和日志文件两头。我之前就吃过只设了DB_FILE_NAME_CONVERT、没设LOG_FILE_NAME_CONVERT的亏,虽然当时在线日志路径碰巧一样没暴露,但一旦备库要自己创建新日志文件,立刻就会路径错位。最后别忘了把standby logfile的组数和大小记下来,确认它远大于主库在线日志组数,避免切换瞬间日志缓冲不够用。
6.3 收尾的一点个人体会
这次最深的体会是,DG搭建最麻烦的不是执行那些命令,而是“静默失败”。路径不对、参数没改、密码不一致,这些错误都不一定当时报错,常常要等到MRP启动、GAP出现、切换演练才暴露。备份恢复创建DG又是一条很吃细节的路线,链路上的每一环都有对应的“检查点”,提前把检查清单跑一遍,比启动MRP后慢慢排查高效得多。现在我去任何现场搭DG,都用这套清单先自查一遍,基本把当年那种半夜救火的节奏杜绝了。你如果正准备做同样的事情,建议也先按照这个顺序把环境预检一遍,再动RMAN恢复那一大串命令。