
1. 为什么 Oracle CDC 值得单独拎出来讲做实时数据集成这些年Oracle 一直是个绕不开的硬骨头。MySQL 的 binlog 生态成熟工具链丰富出了问题社区一搜一大把PostgreSQL 的逻辑复制虽然也有坑但好歹是原生能力文档齐全。唯独 Oracle日志体系复杂、权限模型严格、版本差异巨大很多团队第一次做 Oracle 实时同步时几乎都要经历一轮“从入门到放弃”的心理建设。这篇内容就是围绕 Oracle 与 Oracle CDC 展开的实战拆解。核心要回答几个问题Oracle 的日志机制到底是怎么运作的CDC 抓取日志有哪几条技术路线每条路线的适用边界在哪里以及在实际落地时那些文档里不会写、但一定会踩的坑。适合正在做 Oracle 数据实时同步的工程师、数据平台架构师以及需要把 Oracle 数据接进实时数仓或数据湖的团队参考。不管你是刚接触 CDC 概念的新手还是已经用过几款工具但总在 Oracle 上翻车的老手这里应该都能找到对你有用的东西。先说一个基本判断Oracle CDC 的复杂度八成来自 Oracle 本身的日志架构两成来自工具实现。把日志架构吃透后面选型、调优、排错都会顺很多。2. Oracle 日志体系与 CDC 的底层逻辑2.1 Redo Log 与 Archive LogCDC 的数据源头Oracle 的日志体系是 CDC 的根基不理解它就没法理解 CDC 工具的行为。Oracle 的日志分几个层次从内到外依次是 redo log buffer、online redo log、archived redo log如果开了补充日志还有 supplemental log。redo log buffer 是内存中的一块区域事务产生的变更先写到这里再由 LGWR 进程刷到 online redo log 文件。online redo log 是循环使用的一组一组地轮转写满一组就切换下一组。当数据库开启归档模式后被覆盖前的 online redo log 会被 ARCn 进程复制成 archived redo log长期保存。CDC 工具要抓取历史变更读的就是 archived redo log实时性要求高的场景则同时盯着 online redo log 的切换。这里有个关键点redo log 记录的是“物理变更”比如“在某个数据块的某个偏移量写入某些字节”而不是“把 id1 的 name 改成张三”这种逻辑描述。CDC 工具要还原出逻辑变更就必须借助 supplemental logging。默认情况下Oracle 只记录足够做恢复的最小信息比如更新操作可能只记 rowid 和变更后的值不记变更前的值。CDC 要生成完整的 before/after 镜像就得开启 supplemental logging让 Oracle 额外记录主键、唯一键甚至全列的前像。注意开启 database-level supplemental logging 会增加 redo 生成量大概 10% 到 30% 不等取决于表结构和更新模式。生产库上开之前一定要评估归档空间和 I/O 压力。2.2 SCN 与时间戳CDC 的进度坐标CDC 工具怎么知道“读到哪了”靠 SCNSystem Change Number。SCN 是 Oracle 内部单调递增的一个数字每个事务提交时都会分配一个 SCNredo 记录里也带着 SCN。CDC 工具记录上次处理到的 SCN下次从那个位置继续读就能保证不丢不重。但 SCN 有个坑不同数据库的 SCN 不能直接比较RAC 环境下不同实例的 SCN 也可能有偏差。所以跨库同步时CDC 工具通常会用时间戳做辅助定位或者依赖 Oracle 提供的闪回查询能力按时间点取数。实际配置时起始 SCN 的获取方式很关键常见做法是先用dbms_flashback.get_system_change_number拿当前 SCN再往前推一点作为起点避免漏掉启动瞬间的变更。2.3 补充日志的三种粒度与选择Supplemental logging 分三个粒度database 级、table 级、以及针对主键/唯一键/外键的特定类型。database 级一开全库生效省事但影响面大table 级只对指定表生效精准但需要逐表配置。具体类型里SUPPLEMENTAL LOG DATA是基础记录最小补充信息SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS记录主键列的前像SUPPLEMENTAL LOG DATA (ALL) COLUMNS记录所有列的前像最完整但开销最大。CDC 工具通常要求至少开主键级别的补充日志如果表没有主键就得开 ALL COLUMNS否则更新和删除操作无法定位到具体行。我个人的经验是能加主键就加主键实在加不了再开 ALL COLUMNS。ALL COLUMNS 在宽表上会让 redo 体积膨胀得很厉害一张 100 多列的表每次更新都记全列前像归档空间消耗会非常夸张。3. Oracle CDC 的三条主流技术路线3.1 LogMiner官方出品的日志挖掘工具LogMiner 是 Oracle 自带的日志分析工具通过一组 PL/SQL 包和动态视图把 redo/archive log 的内容解析成可读的 SQL 语句。它的优势是官方支持、无需额外授权只要有EXECUTE_CATALOG_ROLE和SELECT ANY TRANSACTION就行、跨版本兼容性好。劣势是性能一般解析速度受限于数据库自身的 CPU 和 I/O而且对 DDL 的捕获能力有限。用 LogMiner 做 CDC 的典型流程是先dbms_logmnr.add_logfile添加要分析的日志文件再dbms_logmnr.start_logmnr指定起始 SCN 或时间然后查v$logmnr_contents拿变更记录。这个视图里SQL_REDO和SQL_UNDO两列就是重放和回滚用的 SQLOPERATION列标明是 INSERT、UPDATE 还是 DELETE。实际用下来LogMiner 有几个明显的限制。一是它解析的是已经落盘的日志实时性取决于日志切换频率通常有分钟级延迟。二是v$logmnr_contents是会话级的多个会话同时分析会互相干扰。三是大事务解析时内存占用高容易触发 PGA 溢出。所以 LogMiner 更适合对实时性要求不高、但要求零额外组件的场景比如 T1 的增量抽取或者数据审计。3.2 XStreamOracle 官方的逻辑复制框架XStream 是 Oracle 在 11g 引入的逻辑复制框架底层还是基于 LogMiner 的日志解析能力但封装成了 API 形式支持 OCI 和 Java 两种接口。它比裸用 LogMiner 更工程化提供了出站服务器outbound server和入站服务器inbound server的概念可以做到接近实时的变更捕获。XStream 的配置相对复杂需要创建 XStream 管理员、配置出站服务器、授予相关权限还要在数据库层面开启GOLDENGATE相关的参数。它的优势是官方支持、性能比 LogMiner 好、支持事务边界识别。劣势是授权费用不低而且配置门槛高出问题排查起来比较费劲。我见过不少团队一开始想用 XStream配到一半发现权限和参数太绕最后又退回 LogMiner 或者转向第三方工具。如果你的团队有 Oracle 原厂支持XStream 是值得考虑的如果全靠自己摸索要有心理准备。3.3 第三方工具以 GoldenGate 和开源方案为代表GoldenGate 是 Oracle 生态里最成熟的 CDC 工具原理是抽取进程读 redo/archive log通过 trail 文件中转再由投递进程写入目标端。它的优点是性能强、支持异构目标、对 DDL 的捕获能力好。缺点是贵而且配置和维护需要专门的学习成本。开源方案里Debezium 的 Oracle connector 是比较流行的选择底层也是基于 LogMiner 或 XStream。Debezium 的优势是生态好、和 Kafka 集成顺滑、社区活跃。劣势是 Oracle connector 的稳定性一直被人诟病尤其是处理大事务和 DDL 变更时容易出问题。选型时我的建议是如果预算充足且追求稳定GoldenGate 是首选如果已经在用 Kafka 生态且能接受一定运维成本Debezium 可以一试如果只是临时同步几张表LogMiner 手写脚本也能凑合。没有银弹关键看场景。4. 实操从零搭建一条 Oracle CDC 链路4.1 环境准备与权限配置假设我们要把一张订单表从源库实时同步到目标库。源库是 Oracle 19c目标端先用一个中间表模拟。第一步是确认源库开了归档模式SELECT log_mode FROM v$database;如果返回NOARCHIVELOG需要切换到归档模式这一步要重启数据库生产环境务必在维护窗口做。切换命令是SHUTDOWN IMMEDIATE; STARTUP MOUNT; ALTER DATABASE ARCHIVELOG; ALTER DATABASE OPEN;接着开启补充日志。最小配置是 database 级加主键补充ALTER DATABASE ADD SUPPLEMENTAL LOG DATA; ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS;然后创建 CDC 专用用户并授权CREATE USER cdc_user IDENTIFIED BY StrongPass123; GRANT CREATE SESSION TO cdc_user; GRANT EXECUTE_CATALOG_ROLE TO cdc_user; GRANT SELECT ANY TRANSACTION TO cdc_user; GRANT SELECT ANY TABLE TO cdc_user; GRANT LOGMINING TO cdc_user;LOGMINING这个角色是 12c 之后引入的专门给 LogMiner 用比直接给DBA安全得多。如果源库是 11g就得用EXECUTE_CATALOG_ROLE加SELECT ANY TRANSACTION组合。提示SELECT ANY TABLE权限比较大如果只同步特定几张表可以改成对具体表的 SELECT 授权减少权限暴露面。4.2 用 LogMiner 抓取变更的完整脚本环境准备好后写一个 LogMiner 会话来抓变更。先查当前 SCN 作为起点SELECT dbms_flashback.get_system_change_number FROM dual;假设拿到的是 12345678我们从这个 SCN 开始分析。先添加日志文件通常把最近的几个 archive log 都加进去BEGIN dbms_logmnr.add_logfile( logfilename /u01/arch/arch_1_100.arc, options dbms_logmnr.NEW ); dbms_logmnr.add_logfile( logfilename /u01/arch/arch_1_101.arc, options dbms_logmnr.ADDFILE ); END; /然后启动 LogMiner指定起始 SCN 和字典模式BEGIN dbms_logmnr.start_logmnr( startscn 12345678, dictfilename /tmp/logmnr_dict.ora, options dbms_logmnr.DICT_FROM_ONLINE_CATALOG ); END; /DICT_FROM_ONLINE_CATALOG表示用当前数据库的在线字典省去单独导出字典文件的步骤适合源库和目标库结构一致的场景。如果要做历史分析或者源库结构已变就得用DICT_FROM_REDO_LOGS或者提前导出的字典文件。启动后查变更SELECT scn, timestamp, operation, seg_owner, table_name, sql_redo FROM v$logmnr_contents WHERE seg_owner ORDER_USER AND table_name ORDERS AND operation IN (INSERT, UPDATE, DELETE) ORDER BY scn;sql_redo列就是可以直接重放的 SQL。实际生产中不会这么裸查而是把结果落到中间表或者直接推给下游消费。查完后记得结束会话BEGIN dbms_logmnr.end_logmnr; END; /4.3 增量拉取的调度与断点续传LogMiner 会话是一次性的要持续同步就得定时调度。常见做法是写一个存储过程每次从上次记录的 SCN 开始分析处理完把最大 SCN 存到一张控制表里。控制表结构大概是这样CREATE TABLE cdc_control ( table_name VARCHAR2(100), last_scn NUMBER, last_update DATE );每次调度时先读last_scn作为start_logmnr的起点处理完把v$logmnr_contents里的最大 SCN 更新回去。这样即使中间停了重启后也能从断点继续不会丢数据。这里有个细节v$logmnr_contents里的 SCN 是变更发生的 SCN但一个事务可能跨多个 SCN。如果按 SCN 切分可能把一个大事务切成两半。稳妥的做法是按事务提交的 SCN 来切或者用COMMITTED_DATA_ONLY选项只取已提交事务。LogMiner 的start_logmnr支持COMMITTED_DATA_ONLY参数加上它就能过滤掉未提交和回滚的事务。4.4 目标端写入与幂等处理变更推到目标端后写入逻辑要考虑幂等。因为 CDC 链路可能重放同一条变更可能被处理两次。最简单的幂等方案是用主键做 upsertINSERT 冲突就改成 UPDATE。如果目标端是关系库可以用MERGE INTO语句MERGE INTO orders_target t USING (SELECT :id AS id, :amount AS amount, :status AS status FROM dual) s ON (t.id s.id) WHEN MATCHED THEN UPDATE SET t.amount s.amount, t.status s.status WHEN NOT MATCHED THEN INSERT (id, amount, status) VALUES (s.id, s.amount, s.status);DELETE 操作则直接按主键删。这样即使同一条变更重复到达最终结果也是一致的。注意如果源表没有主键幂等就无从谈起因为无法定位到具体行。这也是为什么前面强调能加主键就加主键。5. 踩坑实录Oracle CDC 常见问题与排查5.1 补充日志没开全导致更新丢失前像这是最经典的坑。表上开了主键补充日志但更新时只改了非主键列CDC 抓到的sql_redo里WHERE条件只有主键SET里只有变更后的值没有变更前的值。如果下游需要 before/after 对比就抓瞎了。解决办法是开 ALL COLUMNS 补充日志或者至少对需要前像的列单独开。但 ALL COLUMNS 开销大折中方案是只对关键表开非关键表用主键级别。排查方法查v$logmnr_contents的SQL_REDO看 UPDATE 语句的 SET 子句是否包含所有变更列WHERE 子句是否包含足够定位条件。5.2 大事务导致 LogMiner 内存溢出LogMiner 解析大事务时会把整个事务的变更缓存在 PGA 里事务越大内存占用越高。如果 PGA 不够就会报ORA-04036或者直接会话中断。应对措施有几个一是调大PGA_AGGREGATE_TARGET给 LogMiner 留足空间二是用CONTINUOUS_MINE选项让 LogMiner 边读边处理减少内存驻留三是把大事务拆小从业务层面控制单次变更量。如果都不行就得考虑换 XStream 或 GoldenGate它们对大事务的处理更成熟。5.3 DDL 变更导致解析中断LogMiner 对 DDL 的捕获能力有限表结构一变之前的字典信息就失效了后续解析可能报错或者产生错误的 SQL。常见场景是加列、改列类型、重命名表。规避方法是在 DDL 窗口期暂停 CDC等结构稳定后再从新的 SCN 继续。如果业务不允许停就得用支持在线字典刷新的工具比如 GoldenGate 的 DDL 同步功能。Debezium 在这方面也做了不少工作但 Oracle connector 的 DDL 支持仍然是个薄弱环节。5.4 常见问题速查表问题现象可能原因排查方法解决思路更新操作丢失前像补充日志级别不够查 SQL_REDO 的 SET 子句开 ALL COLUMNS 或列级补充日志LogMiner 会话中断PGA 不足或大事务查告警日志和 PGA 使用率调大 PGA 或拆分事务解析报 ORA-01291日志文件缺失查 v$logmnr_logs补齐缺失的 archive log变更延迟越来越大日志切换频繁或解析慢查 v$logmnr_contents 时间戳优化解析频率或换工具目标端数据重复幂等没做好查目标表主键冲突改用 MERGE 或加去重逻辑无主键表无法同步缺少行定位信息查表约束加主键或开 ALL COLUMNS5.5 几个文档里不会写的实操心得第一个心得LogMiner 的start_logmnr如果指定了DICT_FROM_ONLINE_CATALOG源库结构变更后必须重启会话否则字典不刷新。这个在官方文档里提得很隐晦实际踩过才知道。第二个心得v$logmnr_contents里的TIMESTAMP列是数据库时区如果源库和目标库时区不一致做时间对齐时会出问题。建议统一用 SCN 做进度坐标时间戳只做参考。第三个心得archive log 的保留策略要配合 CDC 的消费速度。如果 CDC 消费慢archive log 被 RMAN 删了LogMiner 就断了。生产环境建议给 CDC 单独留一份 archive log 副本或者把 RMAN 的保留策略调长。第四个心得RAC 环境下每个实例的 redo 线程是独立的LogMiner 要分析所有线程的日志才能拿到完整变更。配置时别忘了把thread参数带上否则只能看到部分实例的变更。6. 选型建议与性能调优思路6.1 不同场景下的工具选择小规模、低实时性、预算有限LogMiner 手写脚本够用就行。适合几张表的 T1 同步或者数据审计。中等规模、要求准实时、有 Kafka 生态Debezium Oracle connector配合 Kafka Connect 做管道。适合已经上了 Kafka 的团队。大规模、高实时性、预算充足GoldenGate性能和稳定性都是第一梯队。适合核心业务系统的实时同步。Oracle 原生、不想引入第三方XStream官方支持但配置复杂。适合有原厂支持的团队。6.2 性能调优的几个关键参数LogMiner 的性能瓶颈主要在日志读取和 SQL 重建。调优方向有几个一是加大PGA_AGGREGATE_TARGET给解析留足内存二是减少分析的日志文件数量只加必要的 archive log三是用COMMITTED_DATA_ONLY过滤未提交事务减少无效解析四是把v$logmnr_contents的查询结果尽快落盘避免长时间持有会话。源库层面补充日志的级别直接影响 redo 生成量。主键级别比 ALL COLUMNS 省很多能在业务层面加主键就加。另外_logmnr_max_parallelism这个隐藏参数可以控制 LogMiner 的并行度但生产环境慎调容易引发不可预期的问题。6.3 监控与告警该盯哪些指标CDC 链路的监控要盯几个关键点延迟当前 SCN 和最新 SCN 的差值、吞吐每秒处理的变更数、错误率解析失败或写入失败的次数、资源占用PGA、归档空间、网络带宽。延迟是最重要的指标一旦持续增长就说明消费跟不上生产要么优化解析要么扩容。归档空间也要盯紧CDC 消费慢的时候 archive log 会堆积满了就影响源库正常运行。告警阈值建议延迟超过 5 分钟告警归档空间使用率超过 80% 告警解析错误连续出现 3 次告警。这些阈值可以根据业务容忍度调整。7. 关于 Oracle CDC 的一些个人体会做了这么多年的 Oracle 实时同步最大的感受是Oracle CDC 没有一劳永逸的方案只有适合当前场景的权衡。LogMiner 简单但性能有限XStream 官方但配置复杂GoldenGate 强大但成本高Debezium 生态好但 Oracle 支持还在完善。选型时不要只看功能列表要结合团队的技术栈、运维能力、预算和业务容忍度综合判断。另一个体会是补充日志和主键是 Oracle CDC 的两条生命线。补充日志决定能不能抓到完整变更主键决定能不能准确定位行。这两样没搞好后面工具再强也白搭。所以每次新接一个 Oracle 源我第一件事就是查补充日志配置和表的主键情况把基础打牢。最后分享一个小技巧如果源库不方便开全库补充日志可以只对需要同步的表开表级补充日志用ALTER TABLE ... ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS逐表配置。这样影响面最小也最可控。配合一张配置表记录哪些表开了、开的什么级别后续维护会清晰很多。