事件结构与 op 字段
Debezium 把每次变更输出成一条结构化事件:before 是变更前的行镜像,after 是变更后的行镜像,source 描述来源库表与日志位点,op 标明变更类型,ts_ms 是事件生成时间。消费侧按 op 决定如何写入目标,Kafka 消息键默认是表主键,因此同一主键的变更落在同一分区。
为什么必须按 op 分支处理
只读 after 字段是最常见的错误:删除事件的 after 为空,直接读取会写入空数据或直接报错;而快照事件(op=r)虽然形态像插入,但它表示存量数据的初始读取,把它当成新增事件做累加统计会让指标整体被放大。
op 取值读法
| op | 含义 | before / after |
|---|---|---|
| c | insert(create) | before 为 null,after 为新行 |
| u | update | before 与 after 通常都有值 |
| d | delete | before 为旧行,after 为 null |
| r | 快照读取(snapshot read) | before 为 null,after 为存量行 |
| t | 仅事务边界消息(视配置产生) | 不含行数据,可忽略或用于审计 |
基本信息
| 事件字段 | before、after、source、op、ts_ms |
|---|---|
| 消息键 | 默认是源表主键,保证同键变更同分区 |
| source 内容 | 库表名、binlog 文件名与位点或 GTID |
| 快照事件 | op=r,表示初始存量读取,不是新增数据 |
| 下游约定 | c/r 走 upsert,d 走 delete,u 走 upsert |
典型使用场景
- 数仓 ODS 落地:按 op 决定插入、更新还是删除目标行
- 缓存维护:收到 c 或 u 刷新缓存,收到 d 删除缓存键
- 审计留痕:保留 before 与 after 以记录变更前后内容
- 对账核对:用 source 里的日志位点定位事件对应的源库位置
- 指标统计:过滤掉 op=r 的存量事件,避免指标被初始数据放大
事件示例(op=c 插入)
事件示例(op=c 插入)
{
"before": null,
"after": { "order_id": 1001, "status": "PAID", "amount": "9.90" },
"source": { "db": "app", "table": "orders",
"file": "mysql-bin.000123", "pos": 4567 },
"op": "c",
"ts_ms": 1728612345678
}下游按 op 分支处理
下游按 op 分支处理
-- 伪 SQL:展示分支逻辑,具体语法按所用引擎调整
-- c / u / r:按主键 upsert,重复投递结果一致
INSERT INTO dwd_order (order_id, status, amount)
SELECT after.order_id, after.status, after.amount FROM cdc_stream
WHERE op IN ('c', 'u', 'r');
-- d:按主键删除
DELETE FROM dwd_order
WHERE order_id IN (SELECT before.order_id FROM cdc_stream WHERE op = 'd');
-- 统计类需求:显式排除快照事件,避免存量数据被当成新增
SELECT COUNT(*) FROM cdc_stream WHERE op = 'c';source 里的文件与位点(或 GTID)是事后回溯的关键线索:出现数据疑问时,可以据此定位到源库的哪一段时间、哪个位置,再结合原始日志核查。
把快照事件(op=r)当作新增数据做累计统计,指标会整体被放大到存量规模;初始化阶段通常需要把 op=r 与 op=c 分开统计,或在统计前先完成存量对齐。