不能直接替代limit offset,但可通过子查询或cte配合where rn between实现逻辑分页;需注意order by必须明确、字段应建索引,且性能通常更差。

ROW_NUMBER() 在 MySQL 8.0 中能直接用于分页吗?
不能直接替代 LIMIT OFFSET,但可以配合 WHERE 实现逻辑分页。MySQL 的 ROW_NUMBER() 是窗口函数,它不改变结果集行数,只是给每行加一个序号;而分页真正需要的是“只取第 N 页的 M 行”。所以必须用子查询或 CTE 包裹,再对序号做筛选。
怎么写一个带 ROW_NUMBER() 的分页查询?
典型结构是:外层查 rn BETWEEN ? AND ?,内层用 ROW_NUMBER() OVER (ORDER BY ...) 排序并编号。注意 ORDER BY 必须明确,否则 ROW_NUMBER() 报错。
- 页码从 1 开始,每页 10 条 → 第 3 页对应
rn BETWEEN 21 AND 30 - 必须用子查询或 CTE,不能在 WHERE 中直接调用
ROW_NUMBER()(会报Window function is not allowed in WHERE clause) - ORDER BY 的字段最好有索引,否则排序开销大,尤其数据量大时
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (ORDER BY id DESC) AS rn FROM users ) SELECT id, name, email FROM ranked WHERE rn BETWEEN 21 AND 30;
和 LIMIT OFFSET 相比,ROW_NUMBER() 分页有什么坑?
性能更差,且无法跳过已删除/新增的行——本质是“取排序后第 N 到 M 行”,不是“跳过前 N 行取 M 行”。当数据频繁变更时,两次请求可能漏行或重复。
-
LIMIT 20, 10是偏移式,数据库可利用索引快速定位起点;ROW_NUMBER()必须全表排序再编号,即使只查一页 - 如果
id不连续或有并发写入,rn = 25对应的记录在两次查询间可能已被删,导致结果空洞 - 没有
OFFSET的天然跳过能力,计算BETWEEN范围时容易算错:第 n 页起始序号是(n-1)*size + 1,不是n*size
什么场景下才值得用 ROW_NUMBER() 分页?
仅当需要“严格按复杂排序规则分页,且排序字段无唯一索引”时才考虑。比如按多个字段组合排序(ORDER BY status DESC, updated_at DESC, id ASC),又想支持跳页,而你无法靠复合索引+游标(cursor-based)优化。
- 简单主键分页、时间范围分页,优先用
LIMIT OFFSET或游标分页(WHERE updated_at ) - 如果业务允许“页内稳定但页间可能漂移”,
ROW_NUMBER()可读性略高;但生产环境高并发分页,它大概率成为慢查询源头 - 注意 MySQL 8.0.2 以上才支持 CTE 中的窗口函数,旧小版本会报
This version of MySQL doesn't yet support 'CTE with window functions'
ROW_NUMBER() 看似灵活,但把排序和分页耦合得更紧,出问题时排查路径反而更长。











