上个月帮一个内部数据团队处理“CentOS 7 下 Oracle 11g CDC 配置”这件事,折腾了两三个周末,最后把整套从环境准备到增量实时同步的链路跑通了。CentOS 7 配 Oracle 11g 本来就不在官方推荐组合里,再加 CDC 需求,中间的坑不算少。这篇总结把配置过程和排错经验都整理出来,给同样在这套组合上做数据同步的同学做个参照。
先说 CDC 解决什么问题。业务系统里的订单表、库存表每天都在大量更新,数据仓库、实时大屏、下游分析库都想知道这些变更。如果靠定时全量抽取,数据延迟高、效率低;如果靠应用层埋点改代码,侵入性太强。变更数据捕获(Change Data Capture,CDC)能直接读数据库日志,把 insert、update、delete 操作原样抓出来,再投递给目标端。整个过程中源库几乎无感知,这才是生产环境最想要的效果。
本文章也增加对应实战场景讲解。
1. 方案为什么这样定?
1.1 业务场景与核心需求分析
拿这次的具体案例来说,业务方提供了一套比较典型的“订单+库存”场景,核心表大概在五十张左右,日增数据量不是特别夸张,但更新频率很高,每晚会有批处理大批量变更。客户端要求:数据延迟不能超过一分钟,目标端是另一套数据库,供报表和数据分析用。
在这个前提下,第一反应就是排除“双写”“应用层发消息”这类侵入式方案,因为改业务代码的代价太高,牵扯的团队太多。CDC 方案就成了必然选择。至于用哪种 CDC 技术,下面仔细对比一下。
1.2 Oracle 原生 CDC 的三条实现路线
Oracle 11g 时代,想做 CDC 基本是这三条路线:
| 路线 | 实现方式 | 对源库影响 | 部署成本 | 适用场景 |
|---|---|---|---|---|
| GoldenGate(OGG) | 读取在线日志与归档日志,专职进程捕获 | 极低,只读日志 | 较高,需要单独部署 | 大规模、生产级别的实时同步,主流选择 |
| LogMiner 自己挖 | 用 LogMiner 接口解析日志还原 SQL | 有解析开销,对归档日志有依赖 | 低,无需额外安装软件 | 小规模表、临时性分析、内部工具 |
| 时间戳+触发器 | 表上建触发器,记录变更时间戳和旧值 | 对业务表侵入明显,性能开销大 | 最低 | 几乎只适合功能演示或低压场景 |
从运维角度看,触发器方案只要上线,性能风险基本不可控;LogMiner 方案解析日志本身也有性能损耗,而且对归档日志的保留策略要求很严。OGG 是专门的日志同步工具,架构上把捕获和应用两个角色拆开,源端压力小、目标端可控,是决定用它的根本原因。
1.3 补充日志与归档模式为什么是 CDC 的“地基”
OGG 是基于日志的同步,前提就是数据库把日志写完整。有两个开关必须在配置前确认好:
第一,归档模式必须开启。OGG 的抽取进程有时需要读取旧日志,如果你关了归档,日志被覆盖之后数据就断了,OGG 只能从头再来或者报错。开启方式是:
-- 查看当前信息 archive log list; -- 如果非归档模式,需要调整后重启数据库 shutdown immediate; startup mount; alter database archivelog; alter database open;执行完建议立即做一次全量备份。
第二,补充日志必须开启。默认日志里不会记录“更新前的整行镜像”,没有补充日志,OGG 拿到 update 语句时可能缺少足够的列来执行 where 条件。最保险的做法是开启数据库级补充日志:
alter database add supplemental log data;也可以只对单表开启列级补充日志,但在生产环境中,表多且经常变动时,数据库级补充日志更省心。
2. CentOS 7 上把 Oracle 11g 先跑稳
2.1 系统依赖与内核参数
Oracle 11g 在 CentOS 7 上不属于官方正式支持组合,但这并不代表跑不起来。实际验证下来,把几个关键依赖补齐后,稳定性完全够用。需要先安装这些软件包:
yum install -y binutils compat-libcap1 compat-libstdc++-33 \ gcc gcc-c++ glibc glibc-devel ksh libaio libaio-devel \ libgcc libstdc++ libstdc++-devel libXext libXtst libX11 \ libXau libXi make sysstat unixODBC unixODBC-devel注意 compat-libstdc++-33 在 CentOS 7 的默认源里可能没有,需要先确认源配置。如果没有这个包,Oracle 的安装向导会报缺少依赖,有时候跳过也能装上,但后续运行 OGG 时可能遇到莫名其妙的问题,还是补齐为妙。
内核参数在/etc/sysctl.conf里加:
fs.aio-max-nr = 1048576 fs.file-max = 6815744 kernel.sem = 250 32000 100 128 kernel.shmall = 1073741824 kernel.shmmax = 34359738368 net.core.rmem_default = 262144 net.core.rmem_max = 4194304 net.core.wmem_default = 262144 net.core.wmem_max = 4194304 net.ipv4.ip_local_port_range = 9000 65500然后sysctl -p生效。这些参数是 Oracle 官方针对 Linux x86-64 的推荐值,不需要太纠结,直接拿来用即可。
2.2 关键系统配置:关闭 THP
透明大页(Transparent Huge Pages,THP)是 CentOS 7 默认开启的特性,但它和 Oracle 数据库的交互并不好,经常导致内存分配不稳定、性能抖动。Oracle 官方也明确建议关闭 THP。关闭方式:
echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag但这两个命令重启后会失效,所以要加入/etc/rc.d/rc.local并在 CentOS 7 里赋予执行权限:
chmod +x /etc/rc.d/rc.local有人会问,SELinux 要不要关?实践来看,为了省事,测试环境可以直接setenforce 0,生产环境建议至少设为 permissive,或者把对应的 Oracle 目录都加上 SELinux 上下文,不然监听和数据库进程容易莫名其妙起不来。这次在生产环境直接采用了 permissive 模式,到目前为止没有新的问题。
2.3 数据库用户与网络端口准备
CDC 需要一个专门的数据库账号来连接源库。出于安全考虑,别直接用 system 或 sys 账号干活。创建账号并授权时,OGG 需要的权限有一定要求,下面是常用的一套组合:
create user ogg_user identified by "StrongPass123" default tablespace users temporary tablespace temp; grant connect, resource to ogg_user; grant select any dictionary to ogg_user; grant select any transaction to ogg_user; grant flashback to ogg_user; grant create session to ogg_user;网络方面,数据库监听 1521 端口要保证可用,OGG 的 Manager 端口(例如 7809)也要在防火墙里开放。CentOS 7 里如果开着 firewalld,可以使用:
firewall-cmd --permanent --add-port=1521/tcp firewall-cmd --permanent --add-port=7809/tcp firewall-cmd --reload2.4 归档目录与磁盘空间的前瞻规划
开启归档之后,最容易犯的错是归档目录空间规划不足。假设业务每天产生 10GB 归档日志,而你只留了 20GB 空间,即便 OGG 在消费也已经很危险。建议归档目录独立挂载,并至少按“日常日志量 × 3 天”的动态空间 + 缓冲估算。另外把归档日志的自动清理做好:
-- 查看闪回恢复区和归档目标的空间使用 select * from v$recovery_file_dest;如果用的是db_recovery_file_dest,别忘记设置合理的db_recovery_file_dest_size。OGG 消费归档日志的速度有时候受大事务影响变慢,这时归档空间告急会连锁导致数据库 hang,这是个必须预先控制住的点。
3. GoldenGate 配置实录:从安装到增量同步跑通
3.1 OGG 安装布局与环境变量
OGG 软件包安装很直接,把对应的 Linux x64 版本解压到指定目录即可。建议放在/opt/ogg,不要放在 Oracle 数据库软件的目录里,保持职责分离。安装完成后第一件事是创建子目录:
cd /opt/ogg ./ggsci GGSCI> create subdirs这一步会生成 dirprm、dirrpt、dirtmp、dirchk 等目录结构,后续所有配置都围绕这些目录展开。
OGG 运行需要的环境变量,主要是找到 Oracle 的客户端库。在/etc/profile或者oracle用户的环境变量里设置:
export ORACLE_BASE=/u01/app/oracle export ORACLE_HOME=$ORACLE_BASE/product/11.2.0/dbhome_1 export OGG_HOME=/opt/ogg export PATH=$PATH:$ORACLE_HOME/bin:$OGG_HOME export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$OGG_HOME:$LD_LIBRARY_PATH关于LD_LIBRARY_PATH排在前面还是后面,遇到过这样的坑:如果环境变量里的库搜索顺序不对,OGG 启动时可能加载到系统自带的 libclntsh 版本,导致 OGG 连接数据库直接报 OCI 错误。这里可以直接把$ORACLE_HOME/lib放在最前面,实测最稳。
3.2 Manager 进程:所有进程的总管家
OGG 的 Manager 负责启动、监控、重启其他进程,是所有进程的守护者。配置 Manager 用 GGSCI 命令行:
GGSCI> edit params mgr文件内容写:
PORT 7809 DYNAMICPORTLIST 7810-7830 AUTORESTART EXTRACT *, RETRIES 5, WAITMINUTES 3 PURGEOLDEXTRACTS ./dirdat/*, USECHECKPOINTS, MINKEEPHOURS 12PORT是 OGG 主监听端口,要跟防火墙开的端口对上。DYNAMICPORTLIST是数据投递时使用的动态端口范围。AUTORESTART让 Manager 在抽取进程意外退出时自动拉起,这里 5 次重试、间隔 3 分钟。PURGEOLDEXTRACTS用来自动清理本地 trail 文件,避免磁盘被塞满。
配置完成后启动:
GGSCI> start mgr GGSCI> info mgr看到 status 为 RUNNING,Manager 就位了。
3.3 源端 Extract 抽取进程配置
抽取进程是 OGG 最核心的部分,它直接读取数据库的在线日志和归档日志。开始之前先连接到数据库:
GGSCI> dblogin userid ogg_user@源库服务名, password StrongPass123如果要支持 OGG 对表的补全日志,还需要在源库上执行:
GGSCI> add trandata schema_name.table_name这个命令的作用等同于数据库层面的补充日志增强,它会生成触发器和日志规则,让 OGG 能拿到完整的旧值信息。如果你希望属性定制,也可以手动用 SQL 的方式执行:
alter table schema_name.table_name add supplemental log data (all) columns;再创建抽取进程。参数文件命名要和进程名对应,例如进程名 EXT_ORD,参数文件就是dirprm/EXT_ORD.prm:
GGSCI> edit params EXT_ORD内容大致如下:
EXTRACT EXT_ORD USERID ogg_user@源库服务名, PASSWORD StrongPass123 EXTTRAIL ./dirdat/lt TABLE schema_name.orders; TABLE schema_name.inventory; TABLE schema_name.order_items;关键点是EXTTRAIL指定了本地 trail 文件目录,后续投递进程数据从从这里读。
创建日志记录点:
GGSCI> add extract EXT_ORD, tranlog, begin now GGSCI> add exttrail ./dirdat/lt, extract EXT_ORDtranlog表示这是在线日志抽取方式,begin now表示从当前时刻开始捕获。如果业务需要把存量数据也同步过去,这里要配合后面说的初始化方案。
启动 Extract:
GGSCI> start EXT_ORD GGSCI> info EXT_ORD看 report 显示无 error,而且每秒有事务记录继续追加,说明抽取进程在正常工作。
3.4 Data Pump 投递进程配置
在源端,除了 Extract 进程,通常会再配置一个 Data Pump 进程,负责把本地 trail 传到目标端。这么做的好处是:本地 trail 可以先快速写入,即使目标端网络抖动,Extract 也不会被阻塞。配置如下:
GGSCI> edit params DPMP_ORD内容:
EXTRACT DPMP_ORD PASSTHRU RMTHOST 目标端IP, MGRPORT 7809 RMTTRAIL ./dirdat/lt TABLE schema_name.orders; TABLE schema_name.inventory; TABLE schema_name.order_items;然后添加进程映射:
GGSCI> add extract DPMP_ORD, exttrailsource ./dirdat/lt GGSCI> add rmttrail ./dirdat/lt, extract DPMP_ORD注意这里的exttrailsource指向的是上一节 Extract 进程写入的本地 trail,投递进程会从那里读并转发到目标端。
3.5 目标端 Replicat 应用进程配置
在目标端数据库(同为 Oracle 11g)上,首先要建立对应的 schema 和表结构。如果业务表结构一致,直接用CREATE TABLE AS SELECT初始化就可以;不一致就要靠 OGG 的映射规则来处理。
目标端 OGG 进程叫 Replicat,参数文件如REP_ORD.prm:
REPLICAT REP_ORD USERID ogg_user@目标库服务名, PASSWORD StrongPass123 MAP schema_name.orders, TARGET schema_name.orders; MAP schema_name.inventory, TARGET schema_name.inventory; MAP schema_name.order_items, TARGET schema_name.order_items;这里MAP是源表与目标表的映射关系。如果目标表名、字段名和源表不完全一致,可以加列映射:
MAP schema_name.orders, TARGET report_schema.orders, COLMAP (order_id = order_id, order_status = order_status, create_time = create_time);然后添加 Replicat 进程:
GGSCI> add replicat REP_ORD, exttrail ./dirdat/lt GGSCI> start REP_ORDReplicat 启动后,会读取目标端本地 trail(由源端 Data Pump 传过来)并应用到目标库。
3.6 存量数据初始化方案
增量同步启动后,存量数据还得并进来,这就需要一个初始化方案。OGG 提供几种方式,最常用的是:先在目标端用 expdp/impdp 或 SQL 方式批量导入存量数据,再启动 Extract。关键在于保证“存量导入”和“增量捕获”两个动作的衔接点一致。
操作思路是:
- 先启动 Extract(但不启动 Replicat)。
- 数据库层面用一致性快照导出存量数据(expdp 参数
flashback_time或者闪回查询)。 - 把存量数据导入目标端。
- 启动 Replicat,让增量数据从 checkpoint 位置继续应用。
这里有个容易搞错的口诀:先启动抽取,后启动应用;数据导出的时间点要和抽取进程的 checkpoint 时间一致,否则会出现存量数据与增量数据重叠或丢失。实际生产环境里,通常会选择业务低峰期做初始化,减少出错的概率。
另外,OGG 有个ADD EXTRACT ... BEGIN NOW的含义要搞清楚,它不是指“从现在才收集所有增量”,而是从发出这个命令的记录点开始收集。所以只要初始化数据是同一时刻的快照,衔接时不会丢。
4. 没装 GoldenGate 也能做:LogMiner 轻量方案
4.1 LogMiner 原理和适用边界
如果只是临时需要抓取几张表的变更记录,或者不想引入整套 OGG 组件,Oracle 自带的 LogMiner 也能实现类似效果。LogMiner 的原理是读重做日志和归档日志,通过DBMS_LOGMNR包解析成 SQL 语句,然后从内存视图里查询。
它的局限也很明显:日志解析是在数据库里做的,SQL 级别重建效率低,大事务、大批量任务会把性能拖垮。另外它处理的是“已经提交”的事务,实时性不如 OGG,数据量大了之后维护成本高。所以我的建议是:数据量小、业务容忍秒级甚至分钟级延迟,用它做临时方案没问题;真要长期跑,还是得 OGG。
4.2 快速启用手把手步骤
LogMiner 的配置过程分成三步。
第一步,补充日志还是要开(和 OGG 的道理相同):
alter database add supplemental log data;第二步,构建字典文件。字典的作用是把日志里的内部对象 ID 翻译成可读的表名列名:
begin dbms_logmnr_d.build( dictionary_filename => 'dict.ora', dictionary_location => '/oracle/logmnr' ); end;注意给 Oracle 进程足够的权限来写这个目录。
第三步,选择需要解析的日志文件列表,然后启动 LogMiner:
begin dbms_logmnr.add_logfile( logfilename => '/oracle/oradata/orcl/archivelog/1_1234_1234567890.arc', options => dbms_logmnr.new ); dbms_logmnr.start_logmnr( dictfilename => '/oracle/logmnr/dict.ora', options => dbms_logmnr.dict_from_online_catalog ); end;最后查询变更记录:
select scn, timestamp, seg_owner, seg_name, operation, undo_sql, redo_sql from v$logmnr_contents where seg_name = 'ORDERS' and timestamp between to_date('2024-01-01 00:00:00','yyyy-mm-dd hh24:mi:ss') and to_date('2024-01-02 00:00:00','yyyy-mm-dd hh24:mi:ss') order by scn;从v$logmnr_contents里能看到 redo_sql(重做后的语句片段)和 undo_sql(回滚 SQL)。需要把这两列组合起来还原完整操作。
LogMiner 的实战局限是:解析日志时,如果归档日志很多,v$logmnr_contents的结果集可能巨大,查询时很容易内存溢出,建议加SEG_NAME、时间范围等条件缩小范围。另外它不会自动跟踪 DML 的键值,无法直接当持续同步管道用。这个方案其实是备胎,适合“拉取过去几天某张表的变更”这种即席需求。
5. 生产实战中踩过的坑与排查速查
5.1 OGG 进程老挂/起不来
OGG 进程起不来,最典型的几个现象和原因:
ERROR OGG-00228,管理员说 “Duplicate extract or primary extract name”。原因是进程名重复或者旧进程没清理干净。确认进程列表后delete extract重建即可。- 启动时报告 OCI 错误,大部分是
LD_LIBRARY_PATH没弄好,导致 OGG 加载 Oracle 客户端库失败。把$ORACLE_HOME/lib放最前面,重新开 shell 再试。 - 提示表不存在或没有权限,先确认
dblogin userid连上了哪个用户,并该用户有select any table权限。
5.2 归档日志暴涨,OGG 消费不过来
这个坑在开启 CDC 后才暴露出来。归档日志的生成速度突然变快,往往是 OGG 的抽取进程在某张大表上落了一个超长事务,日志量瞬间放大。排查思路:
- 看
info extract,如果 Redo Logs 列表积压很多文件,说明消费不畅。 - 看 OGG 进程 report,是否有
LOGREADERtimeout 等待。 - 针对大表做优化:分表、控制单事务大小,或者在 OGG 参数里开启并发抽取。
还有一个很容易漏的点:数据库supplemental log data (all) columns如果设置成 ALL,每次更新都会把整行所有列写入日志,日志量可能翻好几倍。只要能满足同步需求,尽量只对必要表用列级补充日志,而不是全局 ALL。
5.3 时间与字符集不一致导致脏数据
CDC 同步过程中,时区不一致的坑非常隐蔽。源库和目标库的系统时区不同,OGG 抽取日志里的时间戳是带时区的,目标端如果不做转换,就会看到数据晚了一小时或者早了一小时。解决方法是源端和目标端统一SYSTEMTIMEZONE,或者在 OGG 映射里显式转换:
MAP schema_name.orders, TARGET schema_name.orders, COLMAP (create_time = @GETENV('DBTIMEZONE'))字符集不一致的坑也见过很多,源库是 ZHS16GBK,目标库是 AL32UTF8,中文出现乱码。OGG 本身不转换字符集,需要提前确认两端数据库字符集能互通,并通过NLS_LANG设置好环境变量。建议在源端:
export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK目标端根据目标库字符集设置,例如:
export NLS_LANG=AMERICAN_AMERICA.AL32UTF85.4 大表初始化失败的处置
初始化五十张大表期间,遇到过 impdp 到一半中断,重新导入时又要重头再来。后来采取的方案是把大表按分区拆成多个任务,每张表单独导出导入,导入完成后先增量对账,再继续后续表。这样即使某个分区失败,影响范围也被隔离了。
初始化衔接时还遇到过数据重复:存量导入完成前,增量进程已经启动,导致目标端既有导入的旧数据,又应用了增量 update/delete,出现主键冲突。这次教训让我明确了流程顺序——先知性启动抽取进程收取增量,再倒存量,再启动复制进程。顺序不能搞反。
5.5 错误信息速查表
| 错误现象 | 可能原因 | 处理动作 |
|---|---|---|
| OGG-00228 | 抽取进程名重复 | 查看并清理旧进程,重建 |
| OGG-01003 | 源表和目标表结构不匹配 | 核对列定义,用 COLMAP 调整映射 |
| OGG-00423 | 目标表不存在或权限不足 | 检查目标端表结构和 OGG 用户权限 |
| ORA-01031 | 权限不足 | 按前面授权清单补 grant |
| OGG-01224 | trail 文件目录空间不足 | 清理 trail 或扩展磁盘 |
| OGG-00731 | 找不到检查点文件 | 从dirchk目录恢复或重建 checkpoint |
| ORA-00600 内部错误(归档解析) | 多为版本兼容问题 | 确认 OGG 与数据库版本匹配,升级 OGG 补丁 |
6. 个人实操体会
这套组合从选型到上线,我最大的体会是:CDC 项目往往不是难在配置本身,而是难在环境提前量的控制。CentOS 7 + Oracle 11g 本身就是老系统组合,装数据库时如果能一次性把依赖、内核参数、THP、SELinux 都打点好,后面 CDC 的稳定性会省很多事。如果只想着“先把数据库装上再说”,等 OGG 跑起来再回头补环境参数,排查成本会翻倍。
另外,凡是做日志级 CDC,一定要把“归档日志空间”当一等公民看待。很多问题表象是 OGG 进程挂掉,深挖之后发现根因是归档日志把磁盘写满,数据库直接 hang 了。提前想好档案保留策略、监控归档增长趋势,比天天盯进程状态有效得多。
配置完成之后,建议写一个简单的监控脚本,定时检查 OGGinfo all的状态,以及源端归档日志的生成速度。脚本逻辑很简单:把info all里的进程状态和大表归档增量数抓出来,超过阈值就发告警。这一点虽然不是必须,但在生产环境里绝对能救急。