数据同步的陷阱:基于快照的批量处理遗漏问题与 CDC 架构解法

你的数据真的同步完整了吗? 本文直击数据架构的隐形陷阱——基于快照和自增主键水位线的增量同步在 Read Committed 隔离级别下,如何导致延迟提交的事务(如“订单 1002”案例)被批处理任务永久遗漏。我们将深入分析这一问题的数据库根源,并对比两种根本性解决方案:一是基于业务/应用水位线的增量处理,它通过时间戳或版本号来弥补快照缺陷;二是变更数据捕获 (CDC),它通过读取事务日志,彻底实现零遗漏、近实时的数据同步。通过对比侵入性、数据完整性、成本与实时性,助你选择最可靠的流式数据架构策略。

post

Vitah Lin

3 min read
0

/

问题背景

假设系统需要将订单数据从一个在线交易数据库 (OLTP) 增量同步到数据仓库 (DW)。

系统设置:

  • 数据表: Orders (订单表),使用自增主键 Order_ID
  • 同步逻辑: 每 10 分钟运行一个批处理作业,它通过查找上一次处理的最大 Order_ID 来确定本次要处理的新订单。

比如,系统的执行流程如下:

时间点订单事件数据库状态 (Order_ID)批处理状态
T0 (10:00:00)上一个批次成功完成。最大已处理 ID:1000水位线 (Watermark) = 1000
T1 (10:01:00)订单 1001 插入并快速提交Max ID: 1001
T2 (10:02:00)订单 1002 插入,但事务开始Max ID: 1002订单 1002 处于未提交状态
T3 (10:05:00)订单 1003 插入并快速提交Max ID: 1003
T4 (10:10:00)本次批处理启动 (Batch-1)当前最大 ID: 1003
T5 (10:10:05)Batch-1 执行查询: SELECT * FROM Orders WHERE Order_ID > 1000 AND Order_ID <= 1003;数据库隔离级别: 读取已提交的数据。查询结果: 1001, 1003
T6 (10:10:10)订单 1002 事务终于提交。Max ID: 1003订单 1002 变为可见状态
T7 (10:11:00)**Batch-1 完成,**它成功处理了 1001 和 1003。它记录本次处理的最大 ID:1003新的水位线 = 1003
T8 (10:20:00)下一个批处理启动 (Batch -2)Batch-2 查询: SELECT * FROM Orders WHERE Order_ID > 1003

Batch-1 的行为 (T4 - T7)

  • Batch-1 在 T5 询问数据库:“ID > 1000 的新订单有哪些?”
  • 由于数据库的读取已提交 (Read Committed) 隔离级别,订单 1002 在 T5 时尚未提交(事务还在 T2 开始),因此数据库不会返回 Order_ID = 1002。
  • Batch-1 只看到了 1001 和 1003。
  • Batch-1 错误地认为它已经处理了所有 ID 到 1003 的订单,因此将水位线设为 1003

Batch-2 的行为 (T8)

  • Batch-2 启动时,它使用的水位线是 1003
  • 它执行查询:SELECT * FROM Orders WHERE Order_ID > 1003
  • Batch-2 永远不会看到 Order_ID = 1002,因为它查询的范围是从 1004 开始。

注意:订单 1002 被永久遗漏

订单 1002 成功提交并存在于数据库中(T6),但由于它在 Batch-1 查询时处于未提交状态,导致 Batch-1 错误地记录了水位线(1003),将订单 1002 排除在了所有未来的批次范围之外。

解决方案

基于业务/应用水位线 (Application-Managed Watermarking) 的增量处理

对于基于自增 ID/快照的批量处理遗漏问题,尤其是在存在并发事务延迟提交的情况下(如示例中的 Order 1002 遗漏),核心解决方案是放弃基于瞬时最大 ID 的“快照”水位线,通过在源数据表中添加非主键的辅助字段(如时间戳、版本号或状态标记),让应用逻辑在数据写入时更新这些字段,作为水位线。下游批处理任务依据这个水位线来精确地定义增量查询范围。

比如在表 Orders 增加一个字段:last_modified_timestamp,每次记录被 INSERT 或 更新 UPDATE 时,数据库自动将此字段更新为当前的系统时间。批处理任务不再记录上次处理的最大 Order_ID,而是记录上一次成功处理的最大 last_modified_timestamp

变更数据捕获 (Change Data Capture, CDC)

这是数据流/大数据领域最可靠的解决方案,完全绕开了批处理的限制。

核心机制

  • 读取日志: CDC 工具(如 Debezium、GoldenGate)直接监听数据库的事务日志(如 Binlog 或 WAL 日志),而不是查询数据表。
  • 仅处理提交: 数据库日志只记录已提交 (Committed) 的事务。
  • 流式处理: CDC 将日志转换为一个变更事件流 (Stream of Events),这是一种近乎实时的、无遗漏、有顺序的数据流。

如何解决遗漏

  • 订单 1002 场景: 在 T5 时,订单 1002 的事务没有提交,所以日志中没有它的记录
  • 直到 T6 事务提交,日志中才会写入 Order 1002 的 INSERT 事件。
  • CDC 严格按照日志顺序捕获该事件,并将其推送到下游处理,从根本上避免了因查询时机不当而导致的遗漏

总结

下面是关于 “基于业务/应用水位线的增量处理”“变更数据捕获 (CDC)” 这两种核心解决方案的优缺点总结对比表格。

特征基于业务/应用水位线 (Application Watermarking)变更数据捕获 (CDC)
主要优点
实现难度低。 仅需在应用代码和 SQL 中加入逻辑。高。 需引入、配置和运维专门的 CDC 工具/平台。
成本投入极低。 无需购买或部署新工具。高。 可能涉及许可证费用(如 GoldenGate)或额外的基础设施(如 Kafka 集群)。
业务控制强。 业务逻辑完全控制进度和标记,方便定制化。弱。 依赖底层数据库日志,业务逻辑无法干预。
主要缺点
侵入性高侵入性。 必须修改源表 Schema (添加时间戳/版本号等字段) 和应用代码。低侵入性/无侵入性。 不修改源表,仅需数据库权限。
数据完整性有风险。 难以可靠捕获 DELETE 操作;依赖应用代码正确性,有逻辑错误风险。极高。 从日志层面捕获所有 INSERT, UPDATE, DELETE 操作,保证数据完整性。
性能影响增加源数据库的 写入开销(更新标记字段)和 查询开销(全表/索引扫描)。对源数据库影响极小,仅增加日志读取负载。
实时性批处理延迟。 受限于批处理的运行周期(如每 10 分钟或每小时)。近实时 (Near Real-time)。 几乎在事务提交的同时发布事件。
适用场景适用于简单同步、不涉及删除、对延迟不敏感的场景。适用于高并发、高完整性要求、微服务、数据湖等复杂、流式场景。