2、CDC核心原理:三种主流方案深度拆解
CDC(Change Data Capture)说白了就是「怎么把数据库里的变化抓出来」。我在不同项目里用过三种主流方案:基于日志、基于轮询、基于触发器。每种方案都有自己的脾气,选错了后面会很难受。
今天我把这三种方案的原理、优缺点、适用场景掰开揉碎了讲。你想想看,一个每天几千万笔交易的电商系统,跟一个只有几百条配置数据的后台管理工具,能用同一种CDC方案吗?显然不能。
2.1 基于日志的CDC(Log-based CDC)
这是目前生产环境里用得最多的方案。核心思路很简单:数据库每次写操作都会记日志(比如MySQL的binlog、PostgreSQL的WAL),我们直接去读这些日志就行了。
工作原理:
- 数据库引擎在提交事务时,把变更记录写入事务日志
- CDC工具(如Canal、Debezium)伪装成从库,拉取日志流
- 解析日志中的行变更、事务边界、DDL操作
- 把解析结果推送到消息队列或直接写入目标系统
核心优势:对源库零侵入,不改变业务代码,不增加查询压力。
我在项目中遇到过一件事:某金融系统要求CDC延迟不能超过200毫秒,而且不能影响主库性能。当时选了Log-based方案,用Debezium监听MySQL binlog,配合Kafka做缓冲,延迟稳定在50毫秒以内。如果换成轮询方案,光查询压力就能把从库打挂。
个人经验:Log-based方案最怕两件事——日志清理策略和DDL变更。我曾经因为binlog过期时间设得太短,导致全量重跑了一次。建议binlog保留至少24小时,DDL变更前先暂停CDC消费。
适用场景:
- 高并发、低延迟要求的在线业务系统
- 源库不能承受额外查询压力
- 需要保证数据变更的完整性和顺序性
2.2 基于轮询的CDC(Polling-based CDC)
轮询方案是最「笨」但也是最通用的方法。说白了就是定时执行SQL查询,把「变了的数据」捞出来。
常见实现方式:
- 时间戳轮询:在表里加一个
last_modified字段,每次查大于上次轮询时间的记录 - 版本号轮询:维护一个递增的版本号字段,每次查版本号大于上次记录的记录
- 全表比对:每次拉全表数据,跟内存里的快照做diff(不推荐,性能极差)
-- 时间戳轮询示例(每5秒执行一次)
SELECT * FROM orders
WHERE last_modified > '2024-01-15 10:00:00'
AND last_modified <= '2024-01-15 10:00:05'
ORDER BY last_modified ASC
LIMIT 1000;
注意:时间戳轮询有个坑——如果同一秒内有多条记录,可能会漏掉或重复。我建议把时间戳精度提高到毫秒级,或者配合自增ID做去重。
轮询方案最大的问题是延迟和性能。你想想看,轮询间隔设1秒,延迟至少1秒;设100毫秒,数据库查询压力就上来了。我曾经在一个日活百万的论坛系统里用轮询方案,轮询间隔3秒,结果高峰期数据库CPU直接飙到80%。后来不得不改成Log-based。
适用场景:
- 数据量小、变更频率低的系统(比如配置中心、字典表)
- 源库不支持日志解析(比如某些老旧的数据库)
- 对延迟不敏感的后台分析场景
2.3 基于触发器的CDC(Trigger-based CDC)
触发器方案是在数据库里建触发器,每次INSERT、UPDATE、DELETE操作触发时,把变更记录写到一张专门的变更日志表里。
实现步骤:
- 在源表上创建AFTER INSERT/UPDATE/DELETE触发器
- 触发器把变更数据(旧值、新值、操作类型、时间戳)写入变更日志表
- CDC消费者定期从变更日志表拉取数据
- 消费完成后标记或删除已处理记录
-- MySQL触发器示例
CREATE TRIGGER trg_orders_cdc
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT INTO cdc_orders_log(
operation_type,
order_id,
order_data,
created_at
) VALUES (
'INSERT',
NEW.id,
JSON_OBJECT('order_no', NEW.order_no, 'amount', NEW.amount),
NOW()
);
END;
核心问题:触发器会拖慢源库的写入性能。每个DML操作都要额外执行一次触发器逻辑,在高并发场景下影响明显。
我记得有一次帮客户排查性能问题,他们的订单写入接口平均耗时从50毫秒涨到了200毫秒。查了半天,发现是CDC触发器里做了JSON序列化和跨表写入。后来把触发器改成只记录主键和操作类型,其他信息由消费者通过主键回查补齐,性能才恢复正常。
适用场景:
- 对实时性要求不高、写入并发低的小型系统
- 数据库不支持日志解析,但又需要比轮询更可靠的变更捕获
- 需要捕获「谁在什么时候改了什么」的审计需求
2.4 三种方案对比
下面这张表是我在实际选型时常用的对比维度,你直接拿去用就行。
| 对比维度 | Log-based | Polling-based | Trigger-based |
|---|---|---|---|
| 对源库影响 | 无影响 | 有查询压力 | 影响写入性能 |
| 实时性 | 毫秒级 | 秒级(取决于轮询间隔) | 秒级(取决于消费频率) |
| 数据完整性 | 高(事务级别) | 中(可能漏数据) | 高(触发器保证) |
| 实现复杂度 | 高(需要解析日志) | 低(SQL查询即可) | 中(需要管理触发器) |
| 维护成本 | 中(关注日志清理) | 低 | 高(触发器影响排查难) |
| 数据库兼容性 | 依赖数据库日志格式 | 所有数据库都支持 | 所有支持触发器的数据库 |
| 典型延迟 | 10-100ms | 1-30s | 1-10s |
我的选型建议:能用Log-based就别用其他两种。如果数据库不支持日志解析,优先考虑轮询方案,触发器方案尽量少用——它就像在业务代码里埋雷,排查问题的时候特别痛苦。
2.5 三种方案核心逻辑对比图
下面这张SVG图把三种方案的流程画清楚了,你可以直观看到它们的区别。
2.6 选型决策指南
说了这么多,到底怎么选?我一般按下面这个思路来决策:
- 先看数据库支持什么:MySQL 5.7+、PostgreSQL 9.4+、Oracle 10g+都支持日志解析。如果用的是SQLite、H2这种嵌入式数据库,只能选轮询或触发器。
- 再看延迟要求:要求秒级以内?必须上Log-based。能接受分钟级?轮询方案更省事。
- 最后看团队能力:Log-based方案需要懂日志格式、懂CDC工具配置,团队里没人搞过的话,学习成本不低。
避坑提醒:我曾经在一个项目里混合使用了Log-based和Trigger-based两种方案——Log-based负责核心交易表,Trigger-based负责审计日志表。结果触发器里的逻辑跟日志解析的顺序对不上,导致数据不一致。后来统一改成Log-based才解决。所以我的建议是:一个系统里尽量只用一种CDC方案,别混用。