水位列与半开区间
用更新时间列做增量是最常见的抽取方式:记录上一次抽取到的最大值(水位),下次只取之后的数据。关键在于区间的写法——使用「大于等于左边界、小于右边界」的半开区间,并把水位推进到本次窗口的结束时间,而不是推进到本次取到的最大时间戳。
为什么边界最容易丢数
同一时间戳可能对应多条记录:如果用「大于上次最大值」来取,同一秒内的其余记录会被永久跳过。反之如果把水位推进到「本次数据的最大时间戳」,那些时间戳相同但尚未落库的记录也会被跳过。用窗口结束时间推进水位可以同时避开这两种情况。
基本信息
| 常用水位列 | updated_at、gmt_modified、自增 ID |
|---|---|
| 水位保存 | 控制表、调度变量或参数文件,不能存在脚本本地 |
| 区间写法 | [last_watermark, window_end),左闭右开 |
| 推进方式 | 任务成功提交后把水位更新为窗口结束时间 |
| 缺失能力 | 无法感知物理删除与时间戳回拨 |
常用水位列对比
| 水位列 | 优点 | 局限 |
|---|---|---|
| 更新时间列 | 贴近业务修改时间,易理解 | 依赖业务维护字段,补录旧时间的数据会漏采 |
| 自增主键 | 单调递增,边界清晰 | 只反映新增,无法捕获更新 |
| 数据库日志位点 | 能捕获增删改,边界精确 | 需要日志权限与配套工具 |
典型使用场景
- 订单、流水等大表的按日增量抽取
- 外部 API 按游标分页拉取数据,游标即水位
- 多任务串联时的断点续跑:记录水位后失败可从此继续
- 补数:把水位回拨到目标日期前一秒,重跑指定区间
- 对账:用水位区间与目标端条数比对,确认没有漏采
增量 SQL 模板
增量 SQL 模板
-- 左闭右开:不会漏掉边界时间戳上的其它记录,也不会重复上一次窗口末尾
SELECT id, amount, updated_at
FROM source_order
WHERE updated_at >= :last_watermark
AND updated_at < :window_end
ORDER BY updated_at;水位更新(控制表)
水位更新(控制表)
-- 数据成功写入目标后才推进水位,顺序不能颠倒
UPDATE etl_watermark
SET last_value = :window_end, -- 推进到窗口结束时间,而不是数据里的最大时间戳
updated_at = NOW()
WHERE job_name = 'order_incr';水位推进与数据写入必须放到同一个成功边界内:先写数据、成功后再更新水位。反过来写的话,任务在两者之间失败会永久跳过这一批数据。
源端时间戳回拨或补录旧时间的数据会漏采:这类记录的时间早于当前水位,永远不会被增量查询覆盖,只能靠定期对账与全量兜底补回来。