直接用ROW_NUMBER()分页会导致重复,因相同排序值的行顺序随机;须先按业务逻辑去重(如GROUP BY或PARTITION BY+rn=1),再编号分页,且跨页应采用键集分页避免OFFSET缺陷。

为什么直接用 ROW_NUMBER() 会重复分页
当你对含重复数据的表(比如多条记录共享同一 user_id)直接套 ROW_NUMBER() OVER (ORDER BY ...) 分页时,相同排序值的行会被随机打乱顺序,导致第 1 页出现的某条 user_id = 100 记录,在第 2 页又因窗口函数重排而再次出现——本质是去重逻辑没落在分页之前。
子查询里先 DISTINCT 再编号,不行
DISTINCT 和 ROW_NUMBER() 不能共存于同一层 SELECT(语法报错),强行在子查询中 SELECT DISTINCT ... FROM t 后再套 ROW_NUMBER(),会丢失原始行信息(比如你本想取每用户最新一条订单,但 DISTINCT user_id 不知道哪条是最新)。
- 错误写法:
SELECT *, ROW_NUMBER() OVER (ORDER BY user_id) rn FROM (SELECT DISTINCT user_id FROM orders) t
- 问题:丢掉了
order_time、amount等关键字段,无法支撑“每个用户取最新订单”这类真实需求
正确做法:用 GROUP BY 或窗口内去重 + ROW_NUMBER()
核心是把“去重逻辑”显式表达为聚合或优先级选择,再编号。常见两种路径:
- 按业务主键
GROUP BY,用MAX(order_time)或MAX(id)拿最新行,再对结果集编号:SELECT *, ROW_NUMBER() OVER (ORDER BY latest_time DESC) rn FROM ( SELECT user_id, MAX(order_time) AS latest_time, MAX(amount) AS amount FROM orders GROUP BY user_id) t
- 用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC)先标出每组第一条,再外层筛选rn = 1,最后重新编号分页:SELECT *, ROW_NUMBER() OVER (ORDER BY user_id) rn FROM ( SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) rn_inner FROM orders ) t WHERE rn_inner = 1) t2
- 注意:第二层
ROW_NUMBER()的ORDER BY必须和分页意图一致(比如按创建时间倒序),否则页与页之间顺序不稳
跨页分页时 OFFSET 的坑
用 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY 看似简洁,但底层仍要扫描前 30 行;如果去重后总行数少(比如 25 条),第 3 页就为空——而用户可能以为数据丢了。更稳的方式是用键集分页(Keyset Pagination),即记住上一页最后一条的 user_id 和 latest_time,下一页查:
WHERE (latest_time, user_id) < (‘2024-05-01’, ‘u999’) ORDER BY latest_time DESC, user_id DESC LIMIT 10
真正难的不是写对语法,而是把“去重策略”和“分页稳定性”绑在一起设计;一旦 PARTITION BY 字段和 ORDER BY 字段没对齐,或者没处理好 NULL 值排序优先级,跨页就会漏数或重复。










