说实话第一次配 OGG 的 DDL 单向复制时我是有点懵的。平时 DML 复制跑了无数遍什么 MAP、EXTTRAIL、REPLICAT 早就烂熟于心可一旦涉及 DDL就好像在一条顺畅的高速公路上突然多了一堆红绿灯——脚本、权限、触发器、版本匹配少一个环节都起不来。这篇就把我最近一次完整的 OGG DDL 单向复制配置全过程记录下来包括原理、步骤、参数、测试方法以及我掉进去过的坑。不管你是刚接触 GoldenGate 的新手还是已经跑过 DML 同步、想补上 DDL 这块拼图的老手照着这篇走一遍至少能少走一大半弯路。1. 配置OGG DDL单向复制前先搞懂它在做什么1.1 为什么需要DDL单向复制日常运维里多套 Oracle 库之间保持表结构一致是一个很常见的诉求。比如开发环境做完表变更测试环境、预发环境的结构也要跟着变又比如数据仓库需要从源端同步表结构但数据走的是另一套 ETL 流程。手工去执行 DDL 脚本一次两次还行脚本一多、库一多漏执行、错顺序的情况就难以避免。OGG 的 DDL 单向复制就是专门解决这个问题的源端执行了 CREATE TABLE、ALTER TABLE、DROP TABLE 这类结构变更目标端自动跟着执行不需要登录目标库手工操作。我这次做的“DDL 单向复制”指的是数据流只有一个方向从源库往目标库推送结构变更目标库的 DDL 不反向影响源库。和双向复制、双主复制那些复杂拓扑相比单向复制逻辑最简单、排查最容易也是大多数结构同步场景的首选。另一个容易混淆的点是“只同步 DDL、不同步 DML”。需求方明确说数据内容不用动只要把表结构照着源端改过去这样 Replicat 端就必须把 DML 操作过滤掉只应用 DDL。这个需求拆解清楚之后配置思路就非常清晰了源端负责抓 DDL目标端负责执行 DDLDML 数据在目标端直接忽略。1.2 DDL复制的底层逻辑trigger trail replicat刚开始我也有个疑问OGG 不是靠抽取 redo log 吗DDL 操作又不是 DML日志里怎么体现实际上OGG 的 DDL 复制依赖一套专用的数据库对象核心是一个 DDL 触发器。源库每次执行 DDL触发器会把这个操作记录下来然后 OGG 的 Extract 进程能识别这些记录像抽取普通数据一样把 DDL 事件写入 trail 文件。目标端的 Replicat 进程持续读 trail发现里面是 DDL 事件就动态拼出对应语句在目标库执行。这个机制可以类比成快递分拣Extract 是快递员redo log 是仓库里的一堆包裹而 DDL 触发器是贴在包裹上的特殊标签。DML 包裹没有这个标签快递员按正常流程拿走DDL 包裹贴上标签后快递员知道要单独走一条特殊通道送到目标端。所以整个链条里DDL 触发器是源端的“信号源”没有它Extract 对 DDL 就是睁眼瞎trail 文件是“传送带”负责把 DDL 事件搬运过去Replicat 是“收件员”负责把传过来的 DDL 在目标库落地。哪一环断了同步都会静默失败。1.3 这个方案适合什么场景配置前先想清楚你到底需要什么。OGG DDL 单向复制最适合的场景是源库和目标库都归你管目标库就是源库的一个结构镜像平时只读或只作为测试库没有反向写入压力。典型例子就是“一套代码、多套环境”结构同步用 OGG 自动完成省去手工脚本的维护成本。同样的方案也适合用来做结构变更的审计留痕因为 OGG 日志里会记录 DDL 执行的详细时间、对象、语句比业务侧自己埋点更可靠。反过来如果你的目标是“结构 数据”全量一致那我建议直接做标准 DML 同步再把 DDL 打开没必要单独搞 DDL-only。如果目标库本身有业务写入或者多个源库同时往一个目标库推结构单向复制的简单模型可能就不够用需要引入冲突解决机制复杂度会指数级上升。换句话说先把适用边界划清楚后面配置才不会反复推翻。2. 环境准备与版本规划2.1 本次实验的环境清单OGG 版本差异带来的坑非常多所以先亮环境。这次用的是一套测试环境专门用来验证 DDL 单向复制方案架构非常简单两台独立数据库服务器加一套目标库全部是 Linux 操作系统。数据库一个是 Oracle 19c一个是 Oracle 19cOGG 版本也是 19c原则是源端、目标端的 OGG 大版本保持一致避免两边解析 trail 格式出现兼容性问题。环境项源端目标端数据库版本Oracle 19cOracle 19cOGG版本19c19c操作系统某Linux发行版某Linux发行版同步方向-接收同步内容仅DDL仅DDL之所以强调版本匹配是因为 OGG 的 DDL 支持脚本在不同版本里的行为并不一样。比如早期版本里目标端只需要在 Replicat 参数里写上 DDL INCLUDE ALL 就能跑但到 12c 之后目标端还要额外执行一个补丁脚本否则 Replicat 会报错或者直接忽略 DDL。我这次就是先踩了这个版本的坑后面排错章节会细说。2.2 源端数据库前置条件OGG 抽取数据要从 redo 或者归档日志里找所以源库必须处于归档模式。检查方式很简单SELECT log_mode FROM v$database;发现是 NOARCHIVELOG 的话需要先把库切换成归档模式。这个操作会涉及到重启实例建议提前安排维护窗口。我这次环境本来就是归档模式省了一步。但光有归档还不够OGG 还要能日志里提取完整的前后镜像所以补充日志也要打开。ALTER DATABASE FORCE LOGGING; ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;两个语句执行成功后再查一遍确认状态。另外从 Oracle 11.2.0.4 开始OGG 抽取进程要求数据库开启一个隐藏级别的系统参数不打开的话 Extract 会直接起不来报 OGG-01224 错误。需要在 sysdba 下执行ALTER SYSTEM SET ENABLE_GOLDENGATE_REPLICATIONTRUE SCOPEBOTH;这个参数是动态参数不需要重启但要注意它只影响新启动的日志读取会话。现场改完参数之后我习惯再把数据库重启一次才放心哪怕文档说不用求个稳。2.3 OGG安装目录与账号规划OGG 软件目录我习惯单独规划不跟数据库软件混在一起。一般会创建独立的操作系统用户比如用ogg这个专用账号主目录下再分成几个子目录dirdat放 trail 文件dirprm放参数文件dirrpt放进程报告dirout放 discard 文件。这些目录用GGSCI里的CREATE SUBDIRS命令一键生成没必要手工去建。数据库账号方面源端和目标端都需要一个 OGG 管理账号。我这次统一叫oggadm密码按内部规范的复杂度来设。这个账号在源端负责登录数据库、读取日志数据、读取 DDL 触发器记录在目标端则负责执行 DDL、写入相关数据字典信息。特别提醒一句OGG 管理账号和业务表账号最好分开。让 OGG 账号只拥有同步所需的权限就够了不要图省事直接给 DBA 权限否则一旦 OGG 进程配置出错影响面会非常大。3. 源端配置让Extract能抓到DDL3.1 创建OGG数据库用户与授权源端 OGG 账号需要能访问数据字典、读取日志内容同时要能被 DDL 触发器把事件记录写进日志。最简单的授权组合是给它CONNECT、RESOURCE以及SELECT ANY DICTIONARY三个权限后面两个在抽取阶段基本够用。注意不要把业务表的 DML 权限直接授给它抽取进程不需要也不应该直接操作业务表。CREATE USER oggadm IDENTIFIED BY xxxxxx DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp; GRANT CONNECT, RESOURCE TO oggadm; GRANT SELECT ANY DICTIONARY TO oggadm;这里有个容易忽略的细节如果数据库开了统一审计或者某些安全策略限制了SELECT ANY DICTIONARYOGG 读取数据字典视图时可能会时不时报权限不足。遇到这种情况不要急着给 OGG 账号塞更多权限先检查是否存在策略级别的视图阻断对症下药才靠谱。3.2 运行DDL支持脚本这是整个配置里最容易翻车的一步。OGG 安装目录里带了一批以ddl_开头的 SQL 脚本这些脚本必须以sysdba身份在 SQL*Plus 里按顺序执行分别是ddl_setup.sql、role_setup.sql、ddl_enable.sql。它们的作用是创建 DDL 触发器及其依赖对象、创建供 OGG 账号调用的角色、最后把触发器启用起来。我第一次配置的时候直接跳过了role_setup.sql以为只要建了触发器就行结果 Extract 一启动就报 DDL 事件捕获异常。后来按顺序补齐脚本再执行GRANT GGS_GGSUSER_ROLE TO oggadm;给 OGG 账号授权问题才彻底解决。所以宁可慢一点严格按照下面顺序来-- 以 sysdba 身份登录 SQL*Plus ddl_setup.sql role_setup.sql GRANT GGS_GGSUSER_ROLE TO oggadm; ddl_enable.sql脚本运行过程中会提示输入一些参数比如日志仓库表空间名、是否清空原有 DDL 审计资料等按实际规划填写即可。运行完ddl_enable.sql后建议确认一下触发器状态SELECT trigger_name, status FROM dba_triggers WHERE trigger_name LIKE %GGS%DDL%;确保返回状态是 ENABLED。这一步能提前发现脚本顺序错误不用等到 Extract 启动后才一头雾水。3.3 生成并编辑源端Extract参数源端 Extract 参数是决定“抓什么”的核心文件。我这次命名为EXT1参数文件内容如下EXTRACT EXT1 USERID oggadm源库, PASSWORD ****** EXTTRAIL ./dirdat/et DDL INCLUDE MAPPED DDLOPTIONS REPORT TABLE ggs.*;逐行说明一下。USERID指定 OGG 管理账号登录源库EXTTRAIL声明 trail 文件的位置和前缀DDL INCLUDE MAPPED的意思是只捕获那些被后续 TABLE 映射覆盖到的对象上的 DDLDDLOPTIONS REPORT让 OGG 把捕获到的 DDL 信息记录到报告中方便日后排查。TABLE ggs.*把ggs这个 schema 下的所有对象纳入同步范围。如果你只同步 DDL、不同步 DML可以在这个阶段就把思路定下来。我的做法是在目标端用忽略参数过滤掉 DML源端该抓的 DML 也抓只是不应用这样配置更直观如果你想减少 trail 文件里的无效 DML 流量也可以在源端加一个永假过滤条件但要注意通配符场景下部分版本对WHERE (10)的兼容性一般。综合来看我建议先按正常TABLE配置跑通后面再优化流量不迟。3.4 启动Extract并完成首次校验参数文件写好后进入 GGSCI 工具依次执行GGSCI START MGR GGSCI ADD EXTRACT EXT1, TRANLOG, BEGIN NOW GGSCI ADD EXTTRAIL ./dirdat/et, EXTRACT EXT1 GGSCI START EXT1启动后马上看进程状态GGSCI INFO EXT1正常情况下 EXT1 的状态是 RUNNING并且在下方的 Lag 信息里不会出现持续增长的延迟。然后再看一眼报告文件GGSCI VIEW REPORT EXT1报告里会显示 Extract 启动的初始化参数、读取到的归档日志起点、DDL 触发器是否加载等信息。我这次就是在报告里发现DDL replication is enabled的提示才确认源端没有白配。如果你看到任何关于 DDL trigger 的错误回头检查 3.2 的脚本执行顺序基本都能解决。4. 目标端配置让Replicat能应用DDL4.1 目标端DDL补丁与账号准备目标端数据库同样需要建 OGG 管理账号并且因为 Replicat 会在目标端执行 DDL 语句这个账号的权限比源端要更大一些。只靠 CONNECT、RESOURCE 去执行CREATE TABLE、ALTER TABLE完全不够至少需要额外授CREATE ANY TABLE、ALTER ANY TABLE、DROP ANY TABLE否则目标端一执行 DDL 就报 ORA-01031 权限不足。我这次目标端 OGG 账号建完后的授权组合是CREATE USER oggadm IDENTIFIED BY xxxxxx DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp; GRANT CONNECT, RESOURCE TO oggadm; GRANT CREATE ANY TABLE, ALTER ANY TABLE, DROP ANY TABLE TO oggadm; GRANT SELECT ANY DICTIONARY TO oggadm;目标端除了建账号还有一个非常容易被漏掉的环节执行 DDL 补丁脚本。在 OGG 12c 及之后的版本里目标端需要运行ddl_patch.sql才能正确处理从源端传过来的 DDL 事件。这一步我在第一次配置时完全不知道导致 Replicat 启动正常但源端执行 DDL 后目标端毫无反应日志里也没有明显的报错排查了很久才发现是补丁没打。-- 以 sysdba 身份在目标库 SQL*Plus 中执行 ddl_patch.sql这个脚本会在目标库补充一些用于接收 DDL 的对象和配置。跑完之后不需要像源端那样再开一个 DDL 触发器因为目标端 DDL 是由 Replicat 以外来 SQL 的方式发起的DDL 触发器是给源端用的两个身份完全不一样。4.2 编写目标端Replicat参数目标端 Replicat 参数文件我命名为REP1内容如下REPLICAT REP1 USERID oggadm目标库, PASSWORD ****** DDL INCLUDE ALL IGNOREINSERTS IGNOREUPDATES IGNOREDELETES MAP ggs.*, TARGET ggs.*;逐行拆解一下。DDL INCLUDE ALL表示所有传过来的 DDL 事件都应用这比 MAPPED 更适合“结构全量一致”的需求因为我希望源端 schema 里的任何结构变更都能镜像到目标端。最关键的三个参数是IGNOREINSERTS、IGNOREUPDATES、IGNOREDELETES它们是我实现“只同步 DDL、不同步 DML”的核心武器。DML 记录即使源源不断从 trail 读到Replicat 也会直接丢弃完全不动目标表的数据。MAP ggs.*, TARGET ggs.*定义了 schema 级的映射关系。这里需要注意DDL 是通过MAP匹配来决定要不要执行的所以不能因为只关心 DDL 就不写 MAP。反过来说如果你目标端的 schema 名和源端不一样比如源端是ggs目标端是target_ggs那就要写成MAP ggs.*, TARGET target_ggs.*这样 DDL 落到目标端时 OGG 会自动转换对象名和 schema 名。4.3 启动Replicat与常规检查GGSCI 里执行GGSCI START MGR GGSCI ADD REPLICAT REP1, EXTTRAILSOURCE ./dirdat/et GGSCI START REP1然后查看状态GGSCI INFO REP1RUNNING 只代表进程活着不保证 DDL 应用链路是通的。我习惯继续看报告GGSCI VIEW REPORT REP1重点找几个关键词DDL replication enabled、No errors 等。如果报告里出现“No DDL support”之类的提示大概率是 4.1 的ddl_patch.sql没跑成功。另外千万别忽略ggserr.log它记录的是 OGG 进程层面的错误。有时候 Replicat 状态还是 RUNNING但实际一个 DDL 都没应用错误就藏在ggserr.log里。5. 功能验证从建表删表到字段变更5.1 验证DDL同步的典型场景配置全部完成后必须做一轮完整的 DDL 同步验证。我建议不要只测一个建表就下结论而是把常见的 DDL 类型都过一遍因为不同 DDL 在 OGG 内部的捕获路径差异很大。以下是我在源端依次执行的测试动作和对应预期序号源端操作目标端预期结果1CREATE TABLE ggs.t_user (id NUMBER, name VARCHAR2(30))t_user 表出现在目标端2ALTER TABLE ggs.t_user ADD age NUMBER目标端表结构增加 age 字段3ALTER TABLE ggs.t_user MODIFY name VARCHAR2(100)目标端字段长度变成 1004ALTER TABLE ggs.t_user DROP COLUMN age目标端表字段减少5TRUNCATE TABLE ggs.t_user目标端表被 TRUNCATE6DROP TABLE ggs.t_user目标端表被删除每执行完一个动作就在目标端用数据字典确认一下。以第一条为例-- 源端 CREATE TABLE ggs.t_user (id NUMBER, name VARCHAR2(30)); -- 目标端 SELECT table_name FROM user_tables WHERE table_name T_USER;如果表没出现先不要急用STATS REP1看看 Replicat 是否统计到了 DDL 事务。如果统计事务数在增加说明 DDL 事件已经传到目标端问题出在应用环节如果统计纹丝不动那大概率是源端 Extract 或者 trail 环节出了岔子。这种分层排查法比盲改参数高效得多。还有一类 DDL 容易被忽略就是COMMENT ON TABLE这种注释操作。它也是 DDL而且很多业务团队会用注释记录表含义如果漏同步目标端的注释信息就会慢慢失真。我这次专门测了OGG 对 COMMENT 的捕获一般没有大问题但仍然建议纳入回归清单毕竟版本差异可能导致边界行为不一致。5.2 如何确认同步的不是DMLDDL 单向复制和 DML 同步最容易混乱的地方就是你只配置了 DDL 参数但 MAP 还在那里DML 到底会不会被应用我用了一个很直接的方法验证“只同步 DDL、不同步 DML”的目标到底有没有达成在源端对已验证过的表插入一条数据然后去目标端查同一张表。-- 源端 INSERT INTO ggs.t_user (id, name) VALUES (1, test); COMMIT; -- 目标端 SELECT COUNT(*) FROM ggs.t_user;如果目标端表的记录数是 0说明 Replicat 忽略 DML 的配置是生效的。如果有一条记录说明我加的IGNOREINSERTS等参数没起作用或者版本对此参数的支持有差异需要马上检查参数文件是否漏了、Replicat 有没有重新读取参数。这里有一个很常见的误区有些人改了参数文件后只是用START REP1重启进程但 OGG 只有STOP再START才会重新加载全部参数。我吃过这个亏后来养成了每次改参数必须 STOP 再 START 的习惯。6. 排错经验与高频问题速查6.1 启动报错先看这五个地方OGG 的报错不像普通应用那么直观很多时候错误被日志层层包裹。我总结了一套固定的排查顺序先看ggserr.log里的全局错误再看 Extract 或 Replicat 的报告文件然后看 discard 文件里有没有对象级的执行失败记录最后用STATS对比进程读取和写入的事务量确认卡在哪一段。下面是我这次配置以及往期项目中遇到的高频错误速查表错误信息可能原因解决方案OGG-01224ENABLE_GOLDENGATE_REPLICATION 未打开源库执行参数打开ORA-04098DDL 触发器无效或未启用重跑 ddl_enable.sqlOGG-01028MAP 或 TABLE 映射不匹配检查 schema 和对象名映射ORA-01031OGG 账号权限不足按源端/目标端职责补授权目标端无DDL应用目标端缺 ddl_patch.sql在目标端执行补丁脚本Replicat 状态异常trail 文件路径不存在检查 EXTTRAIL 路径和 ADD 命令其中 OGG-01224 最容易迷惑人因为 Extract 报错时前面的状态信息还是 RUNNING。实际原因就是源库没开那个隐藏参数进程在尝试读取日志时被数据库拒绝。设置参数后需要重启 Extract 才能恢复。除了参数之外检查 DDL 触发器状态也建议做成固定动作。触发器状态如果是 INVALID 或 DISABLEDDDL 复制必然失败而很多时候源头只是脚本执行顺序乱了。6.2 常见坑与规避建议版本匹配是 DDL 复制里最先要确认的硬条件。OGG 的 DDL 支持脚本在不同主版本之间并不完全通用比如 12c 之后一定要跑目标端补丁11g 的经典配置拿到 19c 上就可能静默失败。所以不论你是新装还是收到一个旧环境第一件事就是确认源端、目标端的 OGG 版本和数据库版本都在官方支持矩阵里。目标端脚本缺失是我这次踩得最深的一个坑。没有运行ddl_patch.sql时Replicat 不会报任何显式错误它只是沉默地忽略所有 DDL 事件。这种“病态正常”最磨人查了三四轮日志才发现端倪。除了一次性补跑脚本我还建议在验证阶段加入“强制 DDL 同步测试”也就是反复执行建表、改表确认进程统计里有 DDL 计数否则只能寄希望于生产环境的真实 DDL 来暴露问题那代价就太大了。权限管理上也要守住底线。目标端 OGG 账号能执行 DDL意味着它有权修改目标库结构这是个很高的权限。我建议每个环境的 OGG 账号独立设置密码及时轮换同步范围尽量用 MAP 收敛。如果目标端有多个 schema 只是偶尔同步不要图省事直接配通配符而是把白名单列清楚。这样即使 OGG 进程被误操作影响范围也可控。最后再分享一个我自己的小习惯每次配置完 DDL 单向复制我会同时执行STATS EXT1和STATS REP1确认两边的事务统计都能看到 DDL 计数的增长。如果一段时间后统计数字没有变化优先怀疑捕获或者应用链路断开了。这个习惯帮我避免了好几次“看似正常、实则失联”的尴尬局面。OGG DDL 单向复制本身并不复杂但如果你想真正用稳就得像我这样把原理、流程和排错路径串成一条线去理解而不是只抄一段配置就交差。