增量更新必须依赖时间戳或自增id字段,常见错误是全量覆盖或模糊判断;应使用created_at/updated_at等单调字段并确保非空、有索引,注意时区一致;优先用insert…on conflict或merge实现追加+修正,避免delete+insert;sql需带${biz_date}等动态参数。

增量更新必须依赖时间戳或自增ID字段
没有可排序、可比较的单调递增字段,就谈不上真正可靠的增量更新。常见错误是用 UPDATE 直接覆盖全量数据,或者靠业务侧“昨天跑过了”这种模糊判断——一旦中间某天失败,后续全错。
真实场景中,created_at 或 updated_at 是最常用依据;如果表没这类字段,优先加索引后补上,而不是硬凑其他逻辑。
- 必须确保该字段在源表上非空、有索引(否则
WHERE created_at > ?会全表扫描) - 注意时区问题:数据库时区、ETL任务所在服务器时区、业务约定的“日切点”是否一致(比如用
DATE(created_at)还是DATE(created_at AT TIME ZONE 'Asia/Shanghai')) - 避免用
MAX(id)做增量起点——如果存在批量插入、ID跳号、或软删除导致ID不连续,就会漏数据
INSERT … ON CONFLICT(PostgreSQL)或 MERGE(SQL Server)才是正解
每天只追加新记录、再修正变更记录,不能简单 DELETE + INSERT 全量重刷——汇总表通常被下游报表/BI高频查询,锁表几秒都可能引发报警。
PostgreSQL 推荐用 INSERT … ON CONFLICT DO UPDATE,SQL Server 用 MERGE,MySQL 用 INSERT … ON DUPLICATE KEY UPDATE。
- 目标表必须有唯一约束(如
(stat_date, product_id)),否则冲突检测失效 -
ON CONFLICT的DO UPDATE SET部分,只更新需要变化的列(比如sales_amount = EXCLUDED.sales_amount),别写成SET * = EXCLUDED.*——会覆盖掉人工修正过的值 - MySQL 的
ON DUPLICATE KEY UPDATE不支持子查询,如果要聚合计算(如累加当日销量),得先在子查询里算好再传入
增量SQL必须带明确的日期范围参数
硬编码 WHERE created_at >= '2024-05-10' 是灾难源头。所有调度任务里的 SQL 都该用占位符或变量,由调度器注入当天/昨日日期。
- 推荐参数名统一用
${biz_date}(表示统计截止日),然后写成WHERE created_at >= ${biz_date}::DATE AND created_at - 别用
BETWEEN,它包含两端,容易因毫秒级数据跨日导致重复或遗漏 - 如果源表有分区(如按天分区),务必让 WHERE 条件能命中具体分区,否则扫描成本飙升
第一次初始化和断档恢复要单独处理
增量逻辑跑通后,往往发现历史数据没补全,或某天任务失败导致后续全断。这时候不能直接套用每日SQL——它只会处理“最新一天”,不会往前追溯。
- 初始化时,用
INSERT INTO summary_table SELECT … FROM source_table WHERE created_at ,一次性灌完历史 - 断档恢复需人工指定起止日期,例如重跑 5月8日到5月10日:
WHERE created_at >= '2024-05-08' AND created_at ,并确认目标表对应日期范围数据已清空 - 生产环境建议加校验步骤:跑完后查
SELECT COUNT(*) FROM summary_table WHERE stat_date = ${biz_date},与源表当日增量数比对
实际最难的不是写SQL,是确认“哪条数据该算进哪天”——比如订单支付时间、发货时间、结算时间,业务口径一旦变,整个增量逻辑就得跟着调。这个细节没人帮你兜底,得和产品、财务一起对齐。










