CDC技术选型对比:四种主流方案的优劣分析
做CDC增量同步,选对技术方案是第一步。我见过不少团队,上来就选Debezium,结果业务场景其实只需要定时拉一下,白白增加了运维复杂度。也见过用触发器做全量同步的,生产库直接被拖垮。
今天咱们就把四种主流方案掰开揉碎聊一聊。基于日志、基于轮询、基于时间戳、基于触发器——每种我都踩过坑,也总结了一些实战经验。
核心观点:没有银弹。选型取决于你的数据一致性要求、数据库压力容忍度、以及团队运维能力。
一、基于日志的CDC(Debezium / Canal)
这是目前最主流的方案。说白了,就是让数据库把它的“操作日记”借给你看。MySQL的binlog、PostgreSQL的WAL、MongoDB的oplog,都是这个思路。
Debezium基于Kafka Connect,Canal是阿里开源的Java中间件。两者原理类似:伪装成数据库的从节点,实时接收日志流。
优点
- 对源库无侵入——不需要改表结构,不需要加触发器,不产生额外查询。我在一个日活千万的电商系统上用过,binlog读取对主库性能影响几乎可以忽略。
- 数据一致性高——日志记录了完整的事务上下文,能保证“要么全部成功,要么全部失败”。
- 支持DDL变更——表结构改了也能感知到,这在做数据湖同步时特别有用。
- 延迟极低——毫秒级,适合实时数仓和缓存同步。
缺点
- 运维复杂——Debezium要搭Kafka集群,Canal要维护ZooKeeper。我见过一个小团队,为了跑CDC搭了6个组件,最后出问题都不知道从哪查起。
- 日志保留期限制——MySQL的binlog默认只保留几天。如果下游挂了几天,日志被清理了,就得重新全量同步。
- 版本兼容问题——数据库小版本升级可能导致解析器失效。我记得有一次MySQL从5.7升到8.0,binlog格式变了,Debezium直接报错。
我的建议:如果团队有专职的中间件运维人员,或者公司已经用了Kafka,优先选基于日志的方案。否则,先评估一下你能不能接受它的运维成本。
二、基于轮询的CDC
这是最“笨”但也是最通用的方法。定期执行SQL查询,把新增或变更的数据拉出来。比如每5秒跑一次 SELECT * FROM orders WHERE update_time > last_check。
优点
- 实现简单——写个定时任务就行,不需要额外组件。我最早做数据同步时,一个Spring Scheduled加上JDBC就搞定了。
- 数据库无关——不管MySQL、Oracle还是SQLite,只要支持SQL就能用。
- 易于调试——出问题了直接看SQL执行结果,不像日志方案要翻Kafka消费位点。
缺点
- 对源库有压力——每次轮询都是一次查询。如果表数据量大、轮询频率高,很容易把数据库打满。我曾经见过一个案例,轮询间隔设了1秒,结果生产库CPU直接飙到90%。
- 无法捕获删除操作——除非你有软删除标记,否则物理删除的数据轮询是感知不到的。
- 延迟取决于轮询间隔——设1秒就是1秒延迟,设1分钟就是1分钟。做不到真正的实时。
- 数据重复问题——如果轮询期间数据被多次更新,可能会读到中间状态。需要做幂等处理。
避坑指南:我曾经在一个订单系统里用轮询做同步,没加ORDER BY和唯一约束,结果同一条订单被同步了三次,下游对账直接崩了。记住:轮询方案一定要配合去重机制和状态检查。
三、基于时间戳的CDC
这个方案和轮询有点像,但更“聪明”一点。它在表中增加一个时间戳字段(比如last_modified),每次更新时自动更新这个字段。同步时根据时间戳范围拉取数据。
优点
- 实现简单——比轮询多一个字段而已。很多ORM框架(如MyBatis Plus)自带乐观锁和自动填充,用起来很方便。
- 能捕获更新和新增——只要业务代码正确更新了时间戳,就能感知到。
- 支持增量拉取——可以按时间窗口分批拉,避免一次拉太多。
缺点
- 需要改表结构——必须加时间戳字段。对于老系统,改表可能涉及几十个上下游。
- 依赖业务代码正确性——如果某个更新操作忘了设时间戳,数据就丢了。我在一个金融项目里遇到过,批量更新时用了
UPDATE ... SET ... WHERE,但没更新last_modified,结果那批数据永远同步不过去。 - 无法捕获删除——和轮询一样,物理删除感知不到。
- 时间精度问题——如果同一毫秒内有多次更新,可能会漏掉。MySQL的datetime精度默认到秒,高并发场景下不够用。
我的建议:时间戳方案适合业务逻辑简单、表结构可控、且能接受秒级延迟的场景。如果你能保证每个写操作都正确更新时间戳,这个方案其实很稳。
四、基于触发器的CDC
在数据库表上创建触发器(Trigger),当发生INSERT、UPDATE、DELETE时,自动把变更记录写入一张日志表。同步程序再去读这张日志表。
优点
- 能捕获所有操作——包括删除。触发器在事务内执行,不会漏掉。
- 实时性高——触发器是同步触发的,数据一变,日志表立刻就有记录。
- 不依赖业务代码——只要触发器建好了,不管业务怎么改,都能捕获到。
缺点
- 对源库性能影响大——触发器是在事务内执行的。每次DML操作都要额外写一次日志表,事务时间变长,锁竞争加剧。我见过一个案例,加了触发器后,写入性能直接下降了40%。
- 维护困难——触发器是数据库内部的逻辑,不好调试,不好版本管理。而且如果日志表结构变了,触发器也得跟着改。
- 级联风险——触发器里如果写了复杂的逻辑,或者日志表出了故障,可能导致主表操作失败。
- 数据库迁移麻烦——换数据库时,触发器语法可能不兼容,得重新写。
避坑指南:千万不要在生产核心表上直接加触发器做CDC。我见过最惨的一次,触发器里调了一个存储过程,存储过程又调了外部API,结果API超时,整个事务回滚,业务直接停了10分钟。触发器只适合做轻量级的日志记录,别在里面写复杂逻辑。
五、四种方案对比总表
| 维度 | 基于日志 | 基于轮询 | 基于时间戳 | 基于触发器 |
|---|---|---|---|---|
| 源库侵入性 | 无 | 有(查询压力) | 有(改表结构) | 有(触发器+日志表) |
| 数据一致性 | 高(事务级) | 中(可能重复/遗漏) | 中(依赖时间戳) | 高(事务内写入) |
| 延迟 | 毫秒级 | 秒~分钟级 | 秒级 | 毫秒级 |
| 捕获删除 | 支持 | 不支持 | 不支持 | 支持 |
| 运维复杂度 | 高(Kafka/组件多) | 低 | 低 | 中(触发器管理) |
| 适用场景 | 实时数仓、缓存同步 | 低频同步、小数据量 | 可控业务、非核心系统 | 审计日志、小表同步 |
六、选型决策流程图
下面这张图是我自己总结的选型思路,帮你快速判断该用哪种方案。
七、我的最终建议
如果你问我个人习惯怎么选,我会这么排优先级:
- 首选基于日志——只要团队能hold住Kafka,这是最省心的方案。数据一致性好,延迟低,对源库无侵入。Debezium社区活跃,文档齐全,遇到问题基本都能搜到答案。
- 次选基于时间戳——如果业务简单、表结构可控,而且能接受秒级延迟,时间戳方案性价比很高。不需要额外组件,出了问题也好排查。
- 轮询作为备胎——临时同步、数据量小、或者源库不支持日志的场景下用。但记得加去重和状态检查。
- 触发器尽量不用——除非你只是做审计日志,而且表很小、写入频率很低。否则,触发器带来的性能损失和维护成本,远大于它带来的便利。