位置:首页 > SQL > SQL 用 ROW_NUMBER() 分页为什么会重复数据?

SQL 用 ROW_NUMBER() 分页为什么会重复数据?

时间:2026-08-22  |  作者:骑光打字机  |  阅读:0

根本原因是ORDER BY字段不唯一导致ROW_NUMBER()序号漂移,同一行可能跨页重复出现;必须用子查询/CTE套用、建立匹配索引、避免表达式、防止参数溢出并用唯一列兜底定序。

SQL ROW_NUMBER分页查询为什么会重复数据?

根本原因不是语法写错了,而是 ORDER BY 字段不唯一,导致数据库每次执行时对相同值的行分配不同物理顺序,ROW_NUMBER() 生成的序号随之漂移——同一行在第1页和第2页都可能出现。

ORDER BY 字段重复时,ROW_NUMBER() 的序号不固定

只要 ORDER BY 中存在重复值(比如多个订单的 created_at = '2026-08-10 14:30:00'),数据库就无法保证这些行之间的相对顺序。这不是 bug,是 SQL 标准允许的未定义行为。

  • 第一次查询可能把 id = 105 排在同时间组的第1位,分配 rn = 101
  • 第二次查询它被排到第3位,变成 rn = 103,于是出现在下一页
  • 你看到的现象:第1页末尾有 id=105,第2页开头又有 id=105

WHERE 条件写错位置,导致全表编号再过滤

不少人会在窗口函数同层直接写 WHERE rn BETWEEN 21 AND 40,可大多数数据库(像SQL Server、PostgreSQL)无法把这个条件下推到窗口计算之前。最终结果是先对全表进行排序编号,然后再过滤。这样一来,不仅性能差、容易导致内存爆满,而且排序过程本身也不稳定。

  • 必须用子查询或 CTE 套一层:
    SELECT * FROM (
    SELECT *, ROW_NUMBER() OVER (ORDER BY created_at DESC, id DESC) AS rn
    FROM orders
    ) t WHERE t.rn BETWEEN 21 AND 40
  • rn 别名不能省,漏写会报 Invalid column name 'rn'
  • 别用 rn > 20 AND rn <= 40 替代 BETWEEN,部分优化器识别不到 TopN 提示

索引没对齐 ORDER BY,强制走 Sort 算子

就算你写了 ORDER BY created_at DESC, id DESC,可要是没建对应的联合索引,数据库就只能全表扫描再加上内存排序(Sort),这样一来,I/O和tempdb的压力会瞬间增大,深分页时延迟会急剧上升,而且排序过程本身就不太稳定。

  • 索引必须严格匹配 OVER 里的字段顺序和方向:CREATE INDEX IX_orders_time_id ON orders(created_at DESC, id DESC)
  • 如果常带 WHERE status = 'paid',把 status 放索引最左列:CREATE INDEX IX_orders_status_time_id ON orders(status, created_at DESC, id DESC)
  • 避免在排序字段上用表达式,比如 ORDER BY DATE(created_at)UPPER(name)——索引直接失效

参数计算溢出或越界,让 rn 范围错位

用户传参 @page_index = 0@page_size = 1000 时,(@page_index - 1) * @page_size 可能算出负数或超 int 上限,导致 rn BETWEEN -1 AND 99 这种非法范围。

  • DECLARE @offset BIGINT = CAST(@page_index AS BIGINT) - 1 避免 int 溢出
  • 页码兜底:DECLARE @page_index INT = ISNULL(NULLIF(@input_page, 0), 1)
  • @page_size 建议硬限制 ≤ 100,防止恶意请求触发内存 grant 不足或排序溢出 tempdb

最容易被忽略的一点:你以为加了 ORDER BY 就万事大吉,其实它只是“排序”,不是“定序”——只有加上唯一列兜底,才真正让每一行在逻辑序列中有确定位置。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多