问题背景
假设系统需要将订单数据从一个在线交易数据库 (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)。 几乎在事务提交的同时发布事件。 |
| 适用场景 | 适用于简单同步、不涉及删除、对延迟不敏感的场景。 | 适用于高并发、高完整性要求、微服务、数据湖等复杂、流式场景。 |






