增量同步原理:全量同步 vs 增量同步、基于日志的增量捕获(CDC)、基于时间戳的增量同步

大家好,我是老赵。今天咱们聊聊增量同步的核心原理。说实话,很多刚入行的朋友一上来就问我:“老赵,为什么不能每次都全量同步?多省事啊!”嗯,这个问题我当年也问过,直到我在一个日活千万的电商项目里,亲眼看着全量同步把生产库拖垮了……从那以后,我就再也不敢小看增量同步了。

全量同步 vs 增量同步:两个极端

先说说全量同步。说白了,就是把源端的数据整个复制一份到目标端。听起来简单粗暴,对吧?但代价也很大。

全量同步的典型场景: 初次搭建灾备环境、数据恢复后的重新对齐、小数据量的定时备份。

我个人的习惯是:能用增量,绝不用全量。除非数据量小于 10GB,或者这是第一次做同步。为什么?你想想看,一个 2TB 的数据库,全量同步一次可能要跑 8 小时,期间源库的 IO 被打满,业务直接受影响。我在项目中遇到过,全量同步导致主库响应时间从 5ms 飙升到 2s,差点被运维同学拉去“喝茶”。

对比维度 全量同步 增量同步
数据量 全部数据 仅变更数据
同步周期 长(小时级) 短(秒级/分钟级)
源库压力 极高(全表扫描) 低(仅读取变更)
断点续传 需重新开始 天然支持
适用场景 初始化、数据修复 持续同步、容灾

增量同步的核心思路就一句话:只传输变化的数据。但问题来了——怎么知道哪些数据变了?这就引出了两种主流方案:基于日志的增量捕获(CDC)和基于时间戳的增量同步。

基于日志的增量捕获(CDC)

CDC,全称 Change Data Capture。我个人认为,这是目前最靠谱的增量同步方案。它的原理很简单:数据库的每一次写入、更新、删除,都会记录在事务日志里(比如 MySQL 的 binlog、PostgreSQL 的 WAL)。我们只需要解析这些日志,就能拿到精确的变更数据。

我的经验: 在金融项目中,我强制要求使用 CDC。因为基于日志的捕获是“物理级”的,不会漏掉任何一条变更,哪怕是一条回滚的事务,日志里也有记录。这对于对账和审计来说,太重要了。

CDC 的工作流程大致如下:

  1. 日志读取器:连接到源库,实时读取事务日志。
  2. 日志解析器:将二进制日志解析成结构化数据(INSERT、UPDATE、DELETE)。
  3. 变更分发器:将解析后的变更事件发送到目标端(消息队列或直接写入)。
  4. 目标应用器:在目标库上回放这些变更。

这里有个坑,我曾经踩过:日志的消费位置。如果 CDC 进程挂了,重启后从哪里开始读?答案是:记录日志的偏移量(offset)或 LSN(Log Sequence Number)。只要把偏移量持久化到外部存储(比如 ZooKeeper 或数据库),就能实现断点续传。

警告: 不要依赖 CDC 工具的默认内存缓存偏移量。我曾经遇到过,CDC 进程 OOM 重启后,偏移量丢失,导致重复消费了 30 分钟的日志,目标库多出了几百万条重复数据。修复花了整整一个通宵。

下面这张图,是我常用的 CDC 架构,你可以直观地看到数据流向:

CDC 增量同步架构图 源数据库 事务日志 CDC 日志捕获器 解析 binlog/WAL 偏移量存储 消息队列 Kafka / Pulsar 目标数据库 CDC 架构:日志捕获 → 解析 → 分发 → 回放,偏移量持久化保证断点续传

基于时间戳的增量同步

另一种方案,就是基于时间戳。原理更简单:在源表上加一个 update_time 字段,每次同步时,查询“上次同步时间之后修改过的所有记录”。

听起来很美好,对吧?但我在项目中遇到过不少坑:

  • 时间精度问题:如果两条记录在同一毫秒内更新,可能会漏掉一条。
  • 删除操作无法捕获:记录被删了,时间戳字段也没了,你根本不知道它曾经存在过。
  • 大事务延迟:一个事务更新了 10 万行,时间戳都是同一个值,但同步程序可能只读到一部分。

避坑指南: 我曾经在一个 CRM 系统里用时间戳做增量同步,结果因为业务方批量修改数据时没更新 update_time,导致漏掉了 2 万条客户记录。从那以后,我定了一条铁律:时间戳方案必须配合软删除(逻辑删除)和全量对账机制。

下面是一个简单的时间戳增量同步伪代码:

-- 源表结构
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    order_no VARCHAR(64),
    status INT,
    update_time DATETIME(3) INDEX  -- 毫秒精度索引
);

-- 增量查询(假设上次同步时间点为 '2024-01-15 10:00:00.000')
SELECT * FROM orders
WHERE update_time > '2024-01-15 10:00:00.000'
  AND update_time <= NOW()
ORDER BY update_time ASC
LIMIT 1000;  -- 分批处理,避免大事务

注意看,我加了 LIMIT 和 ORDER BY。为什么?因为如果不分批,一次查询几百万条,内存直接爆掉。而且必须按时间排序,否则可能出现“后更新的记录先被消费”的情况,导致目标库数据不一致。

两种方案的对比与选择

说了这么多,到底该选哪个?我个人的决策逻辑是这样的:

判断条件 推荐方案 理由
源库支持事务日志(MySQL/PostgreSQL/Oracle) CDC 无侵入、不丢数据、支持删除
源库是 NoSQL 或老系统,没有日志 时间戳 唯一可行的方案
数据一致性要求极高(金融、对账) CDC 日志是物理级,不会漏
开发资源有限,快速上线 时间戳 实现简单,但要做好补偿机制
需要同步删除操作 CDC 时间戳无法捕获物理删除

我的建议: 如果条件允许,优先上 CDC。虽然初期搭建成本高一些(需要部署解析组件、消息队列),但长期来看,运维成本远低于时间戳方案。你想想看,时间戳方案需要定期对账、处理边界情况、补偿漏掉的删除操作……这些隐性成本加起来,足够你买好几台服务器了。