ROW_NUMBER()分页比LIMIT+OFFSET快,因其在排序后一次遍历打标,避免OFFSET逐行跳过;但需确保ORDER BY有索引、不误用PARTITION BY、且窗口函数不能出现在WHERE中。

窗口函数不是“语法糖”,它绕过了关联子查询最致命的执行模型——逐行触发独立求值。ROW_NUMBER() 配合 OVER() 能把分页从 O(n²) 降为 O(n log n),但前提是别写错框架。
ROW_NUMBER() 分页为什么比 LIMIT + OFFSET 快?
OFFSET 越大,MySQL 越要先扫出前 N 行再丢弃;而 ROW_NUMBER() 是在排序后一次遍历中给每行打标,取第 100001–100100 行时,引擎不用反复跳过前面的数据。
常见错误现象:
- 分页到第 1000 页(OFFSET 99999)时查询突然变慢几秒甚至超时
- EXPLAIN 显示 rows 列是目标页大小的几十倍,说明扫描远超所需
实操建议:
- 用
ROW_NUMBER() OVER (ORDER BY id)生成序号,再套一层子查询过滤:WHERE rn BETWEEN 100001 AND 100100 - ORDER BY 字段必须有索引,否则排序本身就会触发
Using filesort,抵消窗口优势 - 不要在 WHERE 中直接写
ROW_NUMBER() > 100000—— 窗口函数不能出现在 WHERE 子句里
PARTITION BY 会破坏分页逻辑吗?
会。如果你在分页场景里误加 PARTITION BY user_id,ROW_NUMBER() 就变成“每个用户从 1 开始编号”,不再是全局连续序号,分页结果就乱了。
使用场景:
- 纯分页(如后台列表翻页):只用
ORDER BY,不要PARTITION BY - 分组内分页(如查“每个部门工资前 10 的员工”):才需要
PARTITION BY dept_id ORDER BY salary DESC
注意点:
-
PARTITION BY和ORDER BY同时存在时,排序只在每个分区内生效,全局顺序无法保证 - 若需“每个部门取前 10,再按入职时间倒序展示全部结果”,得用两层窗口或 CTE 拆开处理
为什么有时 ROW_NUMBER() 还是比子查询慢?
因为排序成本压倒了算法优势。当 ORDER BY 字段没索引、数据量极大、或内存不足触发磁盘排序时,ROW_NUMBER() 的 Using filesort 开销可能比小结果集的子查询还高。
性能影响关键点:
- 检查
EXPLAIN FORMAT=TREE输出中是否有WINDOW节点和Using temporary标记 - 对大表分页,优先建联合索引:
INDEX(status, created_at, id)(按查询 WHERE 条件 + 排序字段设计) - 避免在窗口函数中用表达式排序,比如
ORDER BY DATE(created_at)—— 会导致索引失效
真正卡住的地方往往不是函数本身,而是 ORDER BY 没走索引,或者误加了 PARTITION BY 把全局序号切碎了。跑之前先看 EXPLAIN,别信“用了窗口函数就一定快”。


















