很多人用 Oracle GoldenGate 做数据同步,基本都是从 DML 复制开始的——INSERT、UPDATE、DELETE 跑通了,就觉得这事儿挺简单。直到有一天上游执行了一条ALTER TABLE ADD COLUMN,下游脚本从凌晨开始报"列不存在",你才会意识到 DDL 这块从头到尾就没被复制。我这次做 OGG 的 DDL 单项复制配置,就是从环境准备、参数编写、启动验证到排错,完整走了一遍,这里把全过程记录下来。无论你是刚接触 OGG,还是已经跑通了 DML 复制想补齐 DDL 能力,这篇都能直接照着操作。
1. 为什么要把 DDL 复制单独拎出来配置
1.1 没有 DDL 复制的同步是一条腿走路
很多数据同步项目在提需求时,业务方只提了"把这几张表的数据实时同步过去",根本没提表结构会不会变。但现实是,只要线上表要增加字段、增加分区、调整索引,数据同步就必须跟着变,否则目标端的表结构永远是旧的。
我之前负责一个报表同步需求,源库是 Oracle 19c,目标端也是 Oracle 19c,业务方每个月都会给大表加分区。最初只配了 DML 级表复制,上线后的第一次结构变更就出了问题:上游核心表加了一个字段,目标端的复制进程还在正常跑,但下游报表任务直接报"列不存在"。查了半天才发现,DDL 根本没进入到复制链路里。
从那次开始,我就养成了一个习惯:只要判断源库表结构会变,同步方案里就必须把 DDL 复制纳入设计,而不是等出问题以后靠停机去手工补表结构。
1.2 单项 DDL 复制的边界要先想清楚
单项复制,就是数据流只能从源库单向流到目标库,不在目标端做反向同步。相比双向复制,单项配置链路简单很多,但 DDL 层面的边界必须提前划定:
- Extract 抓取的 DDL 主要是业务 Schema 上的
CREATE、ALTER、DROP等操作 - 目标端 Replicat 会把 DDL 在目标 Schema 上重放一遍
- 系统用户(如 SYS)的变更、OGG 自身支持对象产生的内部 DDL、数据库级对象(表空间、用户)的创建,默认不会复制过去
这里有个容易混淆的地方:Oracle 里的TRUNCATE TABLE属于 DDL,但 OGG 不会自动把它当普通 DDL 复制,需要单独启用GETTRUNCATES参数。这个我在第五部分测试时专门验证了,一开始就忘了配。
2. 动手之前的三层准备:版本、权限、GLOBALS
如果直接照着 DML 复制那套思路去写 DDL 复制参数,十有八九会在启动阶段翻车。DDL 复制依赖一套独立的支持对象和参数,动手前要把三层准备工作做完。
2.1 版本兼容性确认
我这次的组合是源端 Oracle 19c、目标端 Oracle 19c、OGG 19.1。选型时主要确认三件事:
- 源库和目标库的 Oracle 版本尽量一致,跨大版本时要去查 OGG 的兼容性矩阵
- OGG 版本和数据库版本在认证列表内,特别是 12c 之后的多租户架构,OGG 对容器数据库和可插拔数据库的 DDL 支持行为有差异
- 数据库字符集,这个对 DDL 复制本身影响不大,但 DML 复制时如果两端不一致,后续排查问题会很痛苦
提示:如果源库是 CDB/PDB 架构,先确认 DDL 复制打算在容器级做还是在可插拔数据库级做。我这套配置基于普通非多租户库,所以下面的步骤针对的是经典模式。
2.2 数据库用户与权限规划
DDL 复制需要一套专用的 OGG 用户。我建议单独建,不要复用业务账号。这个用户不仅用于登录 OGG 管理进程,还承担 DDL 支持对象的归属,权限按最小化原则给,但必须能访问数据字典。
源端用户我大致是这样建的:
CREATE TABLESPACE ggs_data DATAFILE '/u01/app/oracle/oradata/ORCL/ggs_data.dbf' SIZE 1024M AUTOEXTEND ON NEXT 100M MAXSIZE UNLIMITED; CREATE USER ggs_user IDENTIFIED BY ggs_pass DEFAULT TABLESPACE ggs_data QUOTA UNLIMITED ON ggs_data; GRANT CONNECT, RESOURCE TO ggs_user; GRANT SELECT ANY DICTIONARY TO ggs_user; GRANT SELECT ANY TABLE TO ggs_user; GRANT ALTER ANY TABLE, CREATE ANY TABLE, DROP ANY TABLE TO ggs_user;注意,ALTER ANY TABLE、CREATE ANY TABLE、DROP ANY TABLE这类授权主要是给目标端 Replicat 执行 DDL 用的。源端 Extract 用户侧重读数据字典和日志,但 DDL 捕获机制本身也要求对支持对象有完全访问权。
源端和目标端的 OGG 用户我习惯分开建,密码可以不同,避免把一套超集权限同时暴露在链路的两个节点上。
2.3 安装 DDL 支持对象:最容易漏的一步
OGG 的 DDL 复制不是纯靠日志解析实现的,它依赖源库上一组 DDL 触发器和支持表。安装必须用 SYS 身份执行,普通用户跑不了。
进入 OGG 安装目录,找到ddl_setup.sql、role_setup.sql、ddl_status.sql,执行顺序如下:
cd $OGG_HOME sqlplus / as sysdba在 SQL 里依次执行:
@ddl_setup.sql—— 创建支持对象(触发器、历史表、内部表),过程有交互式提问,默认回车即可@role_setup.sql—— 创建角色GGS_GGSROLE,执行完按提示记下角色名GRANT GGS_GGSROLE TO ggs_user;—— 把角色授给 OGG 用户@ddl_enable.sql—— 启用 DDL 捕获触发器@ddl_status.sql—— 确认所有对象状态正常
我踩过的第一个坑就在这:执行完ddl_setup.sql没等role_setup.sql和授权就开始配 Extract,导致后面 Extract 启动时一直报数据字典访问权限不足。所以顺序别跳,一步做完再做下一步。
注意:如果库里已经存在旧版 DDL 支持对象,
ddl_setup.sql会报"already exists"类错误,需要先清理干净再执行。
2.4 GLOBALS 文件里的关键参数
GLOBALS 文件位于 OGG 安装目录下,DDL 复制场景下几乎必改。在 GGSCI 里执行EDIT PARAMS ./GLOBALS:
GGSCHEMA ggs_user这个参数告诉 OGG,所有 DDL 支持对象都属于ggs_user。这样 DDL 触发器捕获事件时会自动排除ggs_user自身产生的 DDL,避免 OGG 自己的内部操作污染数据流,形成循环捕获。
至于DDLOPTIONS ADDTRANDATA和DDLOPTIONS REPORT,我习惯放在 Extract 进程参数里,而不是 GLOBALS。不同版本的 OGG 对 GLOBALS 里解析 DDLOPTIONS 的行为略有差异,放到进程参数里更可控。ADDTRANDATA的作用是当一张表第一次发生 DDL 时自动为它补补充日志,省得逐表手工执行ADD TRANDATA;REPORT则是把 DDL 明细写进进程 report 文件,排查时有据可查。
3. 源端 Extract 与 Pump:把 DDL 抓出来并送出去
源端配置是 DDL 复制的主战场。除了常规的 Extract,我这次还加了一个 Data Pump 进程,让 Extract 只负责读源库日志和 DDL 捕获结果,再通过 Pump 把 trail 跨网络送到目标端。这样做的好处是网络抖动不会直接拖垮源库的抽取进程。
3.1 配置 Extract 进程
如果是全新环境,先执行CREATE SUBDIRS建好目录结构,然后在 GGSCI 里建本地 trail 的 Extract:
GGSCI> ADD EXTRACT ext1, TRANLOG, BEGIN NOW GGSCI> ADD EXTTRAIL ./dirdat/lt, EXTRACT ext1如果之前已经有 DML 复制的 Extract,可以复用同一个进程,但必须把 DDL 参数加进去。我习惯单独起一个 Extract 进程专门处理 DDL,和 DML 流程分离,排查问题更清晰。
编辑ext1参数:
EXTRACT ext1 USERIDALIAS gg_src_alias EXTTRAIL ./dirdat/lt DDL INCLUDE MAPPED DDLOPTIONS ADDTRANDATA, REPORT TRANLOGOPTIONS EXCLUDETAG 46 TABLE biz.*;这里解释几个关键指令:
DDL INCLUDE MAPPED:只捕获 MAP 语句中匹配到的表对象的 DDL。如果写成DDL INCLUDE ALL,库里所有用户的所有 DDL 都会被抓进来,安全隐患很大,一般不建议。TRANLOGOPTIONS EXCLUDETAG 46:排除由 OGG 自身事务产生的日志标记,避免源端操作被再次捕获造成循环复制。具体的 tag 值以 OGG 版本为准,我这里写的是常见默认值。TABLE biz.*:决定了 DML 同步范围,DDL INCLUDE MAPPED会跟着这套映射走。
用户别名gg_src_alias需要提前配好,可以在 GGSCI 中这样操作:
GGSCI> ALTER CREDENTIALSTORE ADD USER ggs_user, PASSWORD ggs_pass ALIAS gg_src_alias不同 OGG 版本配置凭据库的命令略有差异,但大致是这个形式。配置完可以用INFO CREDENTIALSTORE验证别名是否存在。
3.2 配置 Data Pump 进程
本地 Extract 抓完日志后,我在同一台机器上建一个数据泵进程:
GGSCI> ADD EXTRACT dp1, EXTTRAILSOURCE ./dirdat/lt GGSCI> ADD RMTTRAIL ./dirdat/rt, EXTRACT dp1dp1 参数文件:
EXTRACT dp1 USERIDALIAS gg_src_alias RMTHOST 192.168.1.20, MGRPORT 7809 RMTTRAIL ./dirdat/rt PASSTHRU TABLE biz.*;这里用PASSTHRU是因为 Pump 进程不需要解析数据内容,它只负责把本地 trail 原样传送到目标端,DDL 信息也会一并携带。
提示:如果不想拆进程,直接在 Extract 里写
RMTHOST加RMTTRAIL也是标准做法,单机场景完全可行。我拆成 Extract 加 Pump,主要是考虑到源库负载较高,希望网络传输和日志抽取解耦。
3.3 启动前的检查与启动
启动前我在 GGSCI 里反复确认这几项:
INFO ALL:Extract 和 Pump 都是 STOPPED 状态VIEW PARAM ext1:DDL 参数确实在参数文件里- 数据库侧用
ddl_status.sql查询支持对象状态,确保都是正常启用
确认无误后,按顺序启动:
GGSCI> START EXTRACT ext1 GGSCI> START EXTRACT dp1启动后立刻看 report:
GGSCI> VIEW REPORT ext1如果看到 DDL replication 已激活的日志信息,说明源端链路就绪。这时候如果报错,九成是第二节的准备没做好,回过去重查。
4. 目标端 Replicat:让 DDL 在目标库落地
源端把 DDL 通过 trail 传过来后,目标端能不能正确执行,取决于 Replicat 参数怎么设计。
4.1 创建 Replicat 进程
目标端同样要建 OGG 用户并授权,但在单向复制场景下不需要安装 DDL 支持对象,因为目标端只是执行 DDL,不是捕获 DDL。若将来要把这个目标库当成新源库再往别处同步,才需要另行安装。
目标端建 Replicat:
GGSCI> ADD REPLICAT rep1, EXTTRAIL ./dirdat/rt编辑 rep1 参数:
REPLICAT rep1 USERIDALIAS gg_tgt_alias DDL INCLUDE MAPPED DDLOPTIONS REPORT DDLERROR DEFAULT ABEND MAP biz.*, TARGET biz.*;这里重点说下DDLERROR。默认情况下,Replicat 碰到目标端执行失败的 DDL 会直接 ABEND,把整个链路停下来等人工处理。好处是问题不会蔓延,坏处是只要一个 DDL 卡住,后面所有数据都积压。
生产环境可以按错误码做细分处理,比如:
DDLERROR 955 IGNORE DDLERROR DEFAULT ABEND上面 955 对应 ORA-00955(名称已被现有对象使用),意思是目标端已有同名对象时跳过这条 DDL,其余错误继续 ABEND。这比一刀切 IGNORE 稳妥。
4.2 MAP 映射对 DDL 复制同样生效
DDL 复制里,MAP 语句不只管 DML,也管 DDL 的对象映射。源端ALTER TABLE biz.orders ADD ...,在目标端执行时会按照MAP biz.*, TARGET biz.*走。如果目标端 schema 叫ods:
MAP biz.*, TARGET ods.*;DDL 复制时会自动做 schema 名转换。如果表名也变了,比如源端biz.orders对应目标端ods.order_info,DDL 复制能不能正确做表名替换,就得翻 OGG 文档确认对象名映射支持情况。我这次为了避免这种复杂度,直接保持两端 schema 名和表名一致。
4.3 表空间子句:提前埋雷不如提前排掉
目标端 Replicat 执行CREATE TABLE时,会连同源端 DDL 语句里的表空间子句一起执行。如果源端和目标端的表空间名不一致,或目标端没有这个表空间,执行就会失败。
我最后的处理是在 Replicat 参数里加上:
DDLOPTIONS NOCREATETABLESPACE这样 DDL 复制时不会把源端的表空间子句带过来,目标端表会落到默认表空间。这个选项在两端表空间管理不一致时几乎必开。
5. 用三个最小测试验证 DDL 复制是否真生效
配置完成后的验证环节,不要上来就模拟复杂变更,从最简单的 DDL 开始逐步加大难度。验证前先看INFO ALL,确认源端 Extract、Pump 和目标端 Replicat 都是 RUNNING 状态,避免进程已经 ABEND 还在做测试。
5.1 CREATE TABLE 验证
在源端执行:
CREATE TABLE biz.test_ddl_1 (id NUMBER PRIMARY KEY, name VARCHAR2(50));稍等几秒,到目标端查:
SELECT table_name FROM dba_tables WHERE table_name = 'TEST_DDL_1';能查到记录,说明 CREATE TABLE 已被复制。再插入几条数据,确认 DML 也没问题。
实操技巧:不用反复登录数据库验证,在 GGSCI 里执行
STATS REPLICAT rep1,看Total DDL operations计数是否增长,就能判断 Replicat 是否处理了 DDL。这个命令比查数据库快得多。
5.2 ALTER TABLE 验证
继续在源端执行:
ALTER TABLE biz.test_ddl_1 ADD (remark VARCHAR2(200));然后到目标端看表结构:
DESC biz.test_ddl_1;目标端出现remark列,说明 ALTER TABLE 同步链路正常。
5.3 DROP TABLE 与 TRUNCATE 验证
最后测试 DROP:
DROP TABLE biz.test_ddl_1;目标端确认表已不存在。
TRUNCATE 要单独验证,因为它不走 DDL 复制链路。需要先在源端 Extract 参数加GETTRUNCATES,目标端 Replicat 参数也加GETTRUNCATES,重启进程后再测试。我第一次配置时忘了这一步,TRUNCATE 没有同步过去,目标端旧数据一直还在,补上参数后重新验证才通过。
6. 实测中踩过的三个坑与完整排查过程
下面这部分是我觉得最值得看的。配置过程本身走一遍就能记住,但运行阶段暴露的问题,才是真正影响稳定性的地方。
6.1 权限不足导致 Extract 无法启动
第一次把整套配置部署到测试环境时,Extract 进程启动后马上 ABEND。查看 report:
OGG-01044 No valid userid found in parameter USERIDALIAS.但我在 GGSCI 里明明用同一个用户登录过。逐项排查后发现,DDL 支持对象安装完后,角色GGS_GGSROLE没有授给ggs_user,导致 Extract 读取 DDL 捕获结果时缺少对内部支持表的访问权限。把角色补上,重启进程后问题消失。
这个坑的教训是:报错表面上是 userid 无效,实际是权限链不完整。以后安装支持对象时,每一步输出都保留下来,最后一定用ddl_status.sql确认对象状态。
6.2 DDL INCLUDE ALL 把无关 DDL 也抓了过来
测试阶段为了省事,我把 DDL 指令写成了DDL INCLUDE ALL。结果源库每天凌晨有几条维护脚本产生的临时表 DDL 也被复制到目标端,目标端多了一批没有任何数据写入的空表。
排查链路是这样的:
- 先在目标端看到多出来的表,第一反应是 MAP 范围写大了,但检查后确认这些表不在
biz.*范围内 - 回到源端看 report,确认这些 DDL 确实是从源库捕获的
- 最后定位到
DDL INCLUDE ALL上,这个指令的语义就是抓取所有 DDL,和 MAP 没有关系
改回DDL INCLUDE MAPPED后,只有映射范围内的表结构变更才会进入复制链路。多出来的空表清理掉,一切恢复正常。
注意:
DDL INCLUDE ALL和DDL INCLUDE MAPPED差异非常大。运维类 DDL 频繁的库上,INCLUDE ALL会带来大量无用同步流量和目标端对象污染。
6.3 目标端表空间缺失导致 DDL 执行失败
第三类问题是目标端 Replicat 在复制 CREATE TABLE 时 ABEND。查看 report,错误信息明确写着表空间TBS_USER不存在。
原因是源端建表语句指定了表空间,而目标端环境的表空间命名完全不同。虽然我前面提到过DDLOPTIONS NOCREATETABLESPACE,但第一轮实际部署时确实忘了加,还是踩到了。
解决办法很简单:在 Replicat 参数加上这行,重启进程,让它继续消费积压的 trail。之后所有 CREATE TABLE 都正常落到默认表空间。
这类问题隐蔽的地方在于,DML 同步完全不受表空间影响,所以很多人不会想到去 DDL 参数里处理表空间子句。只要做 DDL 复制,不管两端表空间是否一致,建议先把NOCREATETABLESPACE加上,能省掉很多麻烦。
排查这三个坑时,我反复用到的命令组合:
VIEW REPORT REPLICAT rep1:看具体错误和 DDL 明细STATS REPLICAT rep1:看操作计数LOGDUMP:怀疑 trail 内容有问题时,直接打开 trail 看具体记录
这三个命令组合起来,基本能把 DDL 复制问题定位到具体环节。
这次配置完成后,我自己定了一条验收纪律:凡是涉及 DDL 复制的项目,清单里必须有"表空间处理方式""TRUNCATE 是否同步""DDL 捕获范围"三项,缺一不可。如果你的 OGG 单项复制也在补 DDL 能力,希望这篇记录能帮你少走几步弯路。