01学完你能做到
- 区分全量、增量、水位线、回看窗口和历史回补。
- 选择 UPSERT、分区覆盖或 MERGE 等幂等写入方式。
- 设计“先写数据、后推进水位线”的安全流程。
02这课解决什么问题
每天全量重拉越来越慢,但上游又可能迟到、修改或删除历史记录。怎样只处理变化部分,同时保证重跑、回补和修正都不会造成重复或漏数?
03增量与水位线
全量是每次重拉全部数据;增量只处理上次之后新增或变化的部分。水位线(watermark)记录上次成功处理到哪里,例如业务日期 2026-10-27 或更新时间 2026-10-28T17:05:00+08:00。
只按业务日期只能发现新的一天,发现不了旧日期被更正。若上游提供 updated_at,可按更新时间取变化;否则可以每次回看最近 N 天,并覆盖写这些日期。
顺序必须是:读取变化 → 写入数据 → 校验和提交 → 推进水位线。水位线先推进、数据后写入,写入失败时下次就会跳过这批数据。
04幂等写入
幂等意味着同一批数据写一次或写十次,最终结果相同。常见方式:
- 按主键 UPSERT:存在则更新,不存在则插入。适合新增和修改,但不知道上游删除了哪条记录。
- 分区覆盖写:在事务中清理并重写一个完整分区,能处理新增、修改和删除,但每次要处理完整分区。
- MERGE:在一条语句中表达插入、更新和删除,具体能力取决于数据库或表格式。
行情日 K 的常见主键是 (trade_date, code)。如果要求目标分区与上游完全一致,分区覆盖比单纯 UPSERT 更稳妥。
05历史回补
回补是主动重算过去一段时间,例如新指标上线补三年历史,或修正复权口径后重算受影响日期。日常增量与历史回补应共用同一套处理逻辑,只改变日期范围和运行参数。
前复权数据尤其要小心:新的分红送转可能改变整段历史的前复权价格,因此不能把前复权序列简单地只追加新的一天。更稳妥的做法是保存不复权价格与公司行为因子,在查询时计算,或在公司行为发生后重算整只股票的受影响区间。
06一个最小例子
-- 主键为 (trade_date, code)
INSERT INTO bars (trade_date, code, close, volume)
SELECT trade_date, code, close, volume
FROM incoming
ON CONFLICT (trade_date, code) DO UPDATE SET
close = excluded.close,
volume = excluded.volume;这段 UPSERT 可吸收新增和修改,但不会自动删除目标表中“上游已经没有”的记录。若要保证某个日期和上游完全一致,应在事务中整体覆盖该日期,或者使用支持删除分支的 MERGE。
07常见误区
- 只按
trade_date增量,历史更正永远进不来。 - 先推进水位线再写数据,失败后静默漏数。
- 把 UPSERT 当成万能方案,忽略删除场景。
- 前复权序列只追加新日期,忽略公司行为会改写历史。
- 回补和日常任务各写一套逻辑,导致口径分叉。
- 回补后不做行数、金额合计和版本对账。
08延伸阅读
09一分钟讲给别人
试着用自己的话讲一遍不可能每天把所有历史数据重拉,所以要记住上次处理到哪里,只取新增和变化。但上游会迟到、更正,甚至删除,所以还要按更新时间或回看窗口补历史。写入必须幂等:直接追加会重复,UPSERT 能更新但删不掉,整日覆盖最容易保证分区和上游一致。日常增量和历史回补应共用一套代码。
5 道自测
先在心里答一遍,再展开对照。能说清理由,比答对数字更重要。
01为什么只按 `trade_date` 做增量会漏数据?
02水位线应该在写数据之前还是之后推进?
03UPSERT 和分区覆盖写的主要区别是什么?
04为什么前复权数据不能只追加新的一天?
05为什么回补要和日常任务共用代码?
已看 0/5 题
练动手练习
任务:假设水位线为 2026-10-27,可用数据包括 2026-10-27、28、29,回看窗口为 2 个已处理日期。列出本次处理日期,并分别说明何时使用 UPSERT、何时使用分区覆盖;运行两次后比较行数和结果。
完成这一课
读完讲义、看完自测答案,这一课就算学完了。
- 读完讲义
- 看完全部自测
本课为投资者教育内容,案例数字均为示意,不构成任何投资建议,也不承诺收益。规则和费率会变,实际操作前请以交易所、监管机构和产品的最新公告为准。