row_number()不能在where中直接使用,因sql执行顺序中where在select(含窗口函数)之前,此时序号尚未生成;必须用子查询或cte先生成rn别名,再外层where rn=1筛选;高并发重复需按业务主键+时间切片(如5秒)partition by,并用ingest_timestamp等可信字段order by确保留最可靠记录。

直接用 ROW_NUMBER() 在 WHERE 里过滤会报错,必须先在子查询或 CTE 中生成序号,再外层筛选 rn = 1;高并发重复消息的“重复”定义往往不是字段完全一致,而是业务主键(如 msg_id + event_time 微秒截断)在极短时间窗口内多次出现。
怎么定义“高并发重复”——PARTITION BY 不能只写 msg_id
单纯 PARTITION BY msg_id 很危险:上游重发、乱序、时钟漂移都可能导致同一条逻辑消息带不同 msg_id,而真正重复的是 order_id + event_type + 时间切片。常见安全写法是把时间精度主动降维:
-
PARTITION BY order_id, event_type, DATE(event_time), FLOOR(EXTRACT(EPOCH FROM event_time) / 5)(PostgreSQL,按5秒切片) -
PARTITION BY order_id, event_type, CONVERT(char(16), event_time, 120)(SQL Server,截到分钟) -
PARTITION BY order_id, event_type, SUBSTRING(event_time, 1, 19)(MySQL,截到秒)
不建议用毫秒级原生时间直接分组——微秒差异就会让本该合并的重复消息散落在不同分区。
ORDER BY 怎么排才能留“最可信”的那条
高并发下,event_time 可能因网络延迟或客户端时钟不准而不可靠,单靠它排序容易留错。优先级应为:
- 第一顺位:
ingest_timestamp DESC(Kafka 消费时间 or Flink 处理时间,服务端统一打点) - 第二顺位:
log_version DESC(上游补发时递增的版本号) - 第三顺位:
id DESC(自增主键,确保物理顺序可回溯)
如果 ingest_timestamp 有 NULL,MySQL 要写成 IFNULL(ingest_timestamp, '1970-01-01') DESC,PostgreSQL 可加 NULLS LAST。
为什么不能用 RANK() 或 DENSE_RANK()
高并发场景下,多条消息可能被同一毫秒级时间戳写入(尤其批量导入或 CDC 同步),RANK() 遇到相同 ingest_timestamp 会返回多个 1,导致去重失效;DENSE_RANK() 同理。只有 ROW_NUMBER() 强制分配唯一序号,哪怕所有排序字段都一样,也会按引擎内部行存储顺序给出确定性结果(只要没 ORDER BY 子句里的不确定性字段)。
DELETE 前必须验证,且别跳过事务
线上删重不是 SELECT 语句跑通就完事。执行前务必:
- 用
SELECT COUNT(*)看将删多少行:SELECT COUNT(*) FROM (SELECT *, ROW_NUMBER() OVER (...) AS rn FROM raw_msgs) t WHERE rn > 1 - 抽样查几条
rn > 1的记录,确认它们确实是重复而非有效重试(比如支付回调的幂等重试) - 用
BEGIN; ... DELETE ...; SELECT ROW_COUNT(); ROLLBACK;先试跑,观察锁表现和耗时
真正容易被忽略的是:高并发写入期间执行 DELETE,可能触发长事务阻塞新消息写入,建议在低峰期或配合应用层限流做。










