Flink SQL Deduplication用 ROW_NUMBER 做流式去重

1. Deduplication 是什么,为什么流式场景尤其需要

Deduplication(去重)是在一组列(去重键)上移除重复行,只保留第一条最后一条 记录。典型原因是:上游 ETL 不是端到端 exactly-once,故障恢复/重试可能把同一业务事件写了多次,导致下游 SUM/COUNT 等统计被"重复行"污染,所以需要在进一步分析前先做去重。(Apache Nightlies)

从实现角度看,Flink SQL 的去重本质上就是 Top-N 的特例:N = 1 ,并且按处理时间或事件时间排序。(Apache Nightlies)

2.1 QUALIFY 写法(更简洁)

官方给出的 Deduplication 语法是:对去重键 PARTITION BY,按时间属性 ORDER BY,保留 ROW_NUMBER() = 1。(Apache Nightlies)

sql 复制代码
SELECT [column_list]
FROM table_name
QUALIFY ROW_NUMBER() OVER (
  PARTITION BY col1[, col2...]
  ORDER BY time_attr [ASC|DESC]
) = 1;

2.2 子查询 + WHERE rownum = 1(更通用,适配更多版本)

很多生产环境更习惯用这版(逻辑更直观),并且必须包含 rownum = 1 ,否则优化器可能无法把它翻译成 Deduplicate 算子。(Apache Nightlies)

sql 复制代码
SELECT [column_list]
FROM (
  SELECT *,
         ROW_NUMBER() OVER (
           PARTITION BY col1[, col2...]
           ORDER BY time_attr [ASC|DESC]
         ) AS rownum
  FROM table_name
)
WHERE rownum = 1;

3. 参数语义:去重键、保留第一条/最后一条、必须是"时间属性"

3.1 PARTITION BY:去重键(deduplicate key)

PARTITION BY col1[, col2...] 就是你判定"重复"的那组字段(比如 order_id、message_id、(user_id, item_id) 等)。(Apache Nightlies)

3.2 ORDER BY time_attr:决定保留第一条还是最后一条

排序列 必须是时间属性 (processing time 或 event time)。(Apache Nightlies)

  • ORDER BY time_attr ASC:保留最早的一条(first row)
  • ORDER BY time_attr DESC:保留最新的一条(last row)(Apache Nightlies)

3.3 处理时间 vs 事件时间:结果是否"可复现"

经验上强烈建议优先用 事件时间(rowtime) 做去重排序:

一些调优/最佳实践文档也明确指出:按 processing time 去重,结果会随运行时机波动;按 event time 去重,结果更确定、更可复现。(Ververica 文檔)

4. 示例:按 order_id 去重,只保留第一次出现(你给的 Orders 例子)

sql 复制代码
CREATE TABLE Orders (
  order_id  STRING,
  user      STRING,
  product   STRING,
  num       BIGINT,
  proctime  AS PROCTIME()
) WITH (...);

-- 按 order_id 去重,保留第一次出现(ASC)
SELECT order_id, user, product, num
FROM (
  SELECT *,
         ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY proctime ASC) AS row_num
  FROM Orders
)
WHERE row_num = 1;

这个模式正是 Flink 官方去重示例中强调的写法:ROW_NUMBER + 分区键 + 时间排序 + rownum = 1。(Apache Nightlies)

5. 去重的 Changelog 语义:为什么下游可能会看到 UPDATE/撤回

流式去重不是"简单过滤一次"------当更早/更晚的记录到来时(尤其事件时间乱序),去重结果可能需要被修正,因此会产生 changelog(更新流)

在 Flink SQL 的 retract 机制里,更新通常会被拆成 DELETE(-U) + INSERT(+U) 两条事件(先撤回旧值,再发新值)。(Confluent)

工程结论:

  • 你的 sink/下游必须能消费更新流(upsert/retract),否则结果会错或写不进去。

6. Sink 选型建议:优先 Upsert 型(尤其写 Kafka / 外部库)

如果你把去重结果写入 Kafka,官方 upsert-kafka 明确支持消费 changelog:会把 INSERT/UPDATE_AFTER 写成正常消息,把 DELETE 写成 tombstone。(Apache Nightlies)

落外部存储(JDBC、KV、OLAP)时,同样建议使用 Upsert 语义(主键一致),让"同一 key 的最新结果"被覆盖更新。

7. 状态与 TTL:去重会"记住 key",不设 TTL 可能越跑越大

去重需要维护"某个 key 当前保留的是哪一条",因此会占用 state。Flink Table/SQL 提供 table.exec.state.ttl(Idle State Retention Time)用来控制 key 的状态多久没更新就清理。(Apache Nightlies)

注意:TTL 会影响正确性(清理太早可能把还会来的迟到数据当成"新 key"),一般需要结合业务延迟/乱序程度来定。

8. 进阶:Window Deduplication(每个窗口内去重)

如果你需要"每个窗口内 按 key 去重"(例如 10 分钟内同一用户只保留最后一次点击),Flink 还有专门的 Window Deduplication 形态:同样要求 ORDER BY 是时间属性,且 rownum = 1/<=1/<2 这种固定谓词,才能让优化器识别。(Apache Nightlies)

9. 一页避坑清单(最常见 5 个坑)

  1. rownum = 1 写错/写丢 → 优化器可能无法识别为 Deduplication。(Apache Nightlies)
  2. ORDER BY 不是时间属性 → 不符合 Deduplication 语义要求。(Apache Nightlies)
  3. 用 processing time 去重却要求结果稳定 → 可能每次跑都不一样。(Ververica 文檔)
  4. 下游不支持更新流(append-only sink)→ 结果不正确或写入失败。(Confluent)
  5. 不设 TTL/不控状态 → key 越来越多时 state 可能持续膨胀。(Apache Nightlies)
相关推荐
大大大大晴天1 小时前
每天认识一个组件:数据治理Apache Atlas
大数据
数字新视界2 小时前
2026模块化机房选型指南:行业市场发展趋势、占有率与竞争梯队分析报告解析
大数据·人工智能·物联网·数据中心·微模块机房·模块化机房·冷通道
姜穆澜4 小时前
OneID 从 0 到 1 完整生产案例(四)
大数据
OsDepK4 小时前
项目快速Git至仓库(完整版)
大数据·git·elasticsearch·搜索引擎
合米AI SOP系统5 小时前
传统产线如何快速上马落地 AI 防错?合米科技 AI SOP 7天即可上线。
大数据·人工智能·科技
思录Echo5 小时前
什么决定具身智能的最终走向?多技术路线与落地现实辨析
大数据·人工智能
xiaohaiAIgeo5 小时前
【2026年】AI监控加行为分析守护实验室安全
大数据·人工智能·科普知识
林墨聊AIGC6 小时前
动漫AI视频创作工具在哪找到的?2026年最新动漫AI视频平台与软件指南
大数据·人工智能·ai作画·aigc·音视频
冰暮流星6 小时前
mysql之左外连接与右外连接
数据库·sql
故七月7 小时前
告别 AI 时代品牌 “隐身”:万域智瞰 AI‑GEO,构建品牌大模型时代营销新基建
大数据·人工智能