首页/数据/数据工程与 ETL 速查/数据去重的常见模式
管道基础52 条

数据去重的常见模式

重复来源:上游重发、至少一次投递、任务重跑。批侧用 ROW_NUMBER 取最新一条;流侧用去重状态或下游 upsert 收敛。

重复数据从哪来

重复几乎不可避免,来源主要有三类:上游系统重发(接口重试、日志采集器重读文件)、消息投递语义为至少一次(故障重放)、以及任务重跑(补数或失败重试)。与其要求上游不重复,不如在下游把去重做成常规能力。

批侧与流侧的差异

批处理有完整数据集,去重直接按业务主键排序取最新一条即可;流处理看不到全集,只能在状态里记住「这个键处理过没有」,因此必须给状态设置过期时间,否则状态会随 key 基数无限增长,最终拖垮作业。

基本信息

批处理去重按主键分组排序取第一条(最新状态)
流式去重按主键维护状态并设置 TTL,或用去重算子
兜底手段落库端按主键 upsert,无论上游重复多少次结果一致
关键前提去重键必须是完整的业务主键
代价流式去重会保留状态,TTL 越长内存占用越大

去重位置怎么选

去重位置优点局限
上游采集端减少下游数据量难以覆盖所有重复来源
加工层 SQL逻辑清晰、可重跑验证只对已到达的数据生效,无法防未来重复
落库端主键 upsert最靠近存储,兜底效果最好需要唯一键与索引支撑
流式状态去重能挡住重复事件进入下游状态成本高,必须设置过期时间

典型使用场景

  • CDC 链路:连接器重启后位点回退,同一变更事件会重复投递
  • 日志采集:采集器文件轮转或重启后重读末尾部分内容
  • 接口拉取:网络超时后重试,实际已成功写入的数据被再拉一次
  • 任务重跑:失败重试后同一分区被处理两遍
  • 多来源合并:两个上游系统上报同一业务实体的记录

SQL 去重取最新

SQL 去重取最新
-- 按业务主键取最新一条(批处理最常用)
SELECT *
FROM (
  SELECT o.*, ROW_NUMBER() OVER (
           PARTITION BY order_id ORDER BY updated_at DESC) AS rn
  FROM ods_order o
) t
WHERE rn = 1;

Flink SQL 去重与状态过期

Flink SQL 去重与状态过期
-- 流式去重:按主键保留第一条,其余丢弃(配合状态 TTL 使用)
SELECT order_id, amount, ts
FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY ts ASC) AS rn
  FROM orders_src
) WHERE rn = 1;

-- 关键:不给状态设置过期时间,去重状态会随 key 基数无限增长
SET 'table.exec.state.ttl' = '24h';   -- 超过该时间未更新的状态会被清理
去重键必须覆盖完整业务主键:用组合主键去重的表只取其中一列,会把不同业务实体的记录误判为重复并删除掉。
流式去重状态不设 TTL 是最常见的生产事故来源:任务前几周运行正常,key 基数上来后状态膨胀,checkpoint 与恢复时间同步失控。

易错点

  • 按 updated_at 排序时存在空值,空值会排到最前或最后,导致取到旧记录甚至误删有效记录
  • 流式去重状态无过期时间,状态随 key 基数无限增长,最终 checkpoint 与恢复时间失控
  • 去重键不完整(组合主键只取一列),不同业务实体被误判为重复并删除
  • 只在加工层去重而不在落库端兜底,后续新增链路或重跑任务时重复数据重新出现

数据来源与参考

  • Apache Hive LanguageManual:Windowing 与 Analytics 函数
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌