ROW_NUMBER()必须配合OVER子句使用,否则报错;其基本形式为ROW_NUMBER() OVER (ORDER BY column),分组需加PARTITION BY,且ORDER BY不可省略以确保排序稳定性和编号唯一性。

ROW_NUMBER() 必须搭配 OVER 子句,否则直接报错
单独写 ROW_NUMBER() 会触发类似 Window function 'ROW_NUMBER' requires an OVER clause 的错误。它不是普通函数,而是窗口函数,必须明确排序逻辑——哪怕业务上不关心顺序,也得写个兜底 ORDER BY id,否则 MySQL 8.0+、PostgreSQL、SQL Server 全都会拒绝执行。
常见踩坑点:
- 用
ORDER BY RAND()做分页会导致每次结果乱序,同一页可能重复或漏数据 - 排序字段没索引,大表分页时
OVER (ORDER BY created_at)会全表扫描,响应从毫秒变秒级 - 多个相同排序值(比如大量
created_at精度为秒)会导致ROW_NUMBER()分配不稳定,翻页时某条记录可能在第2页和第3页都出现
子查询里套 ROW_NUMBER 后再 WHERE 过滤是唯一可靠分页写法
不能把 ROW_NUMBER() 放在最外层 SELECT 再加 WHERE rn BETWEEN 11 AND 20——SQL 执行顺序决定 WHERE 在窗口函数计算前就运行,rn 根本不可见。必须用子查询(或 CTE)先算出行号,再对外层结果过滤。
标准写法示例(取第2页,每页10条):
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM orders WHERE status = 'paid' ) t WHERE t.rn BETWEEN 11 AND 20;
注意点:
-
WHERE status = 'paid'要写在子查询内,提前过滤数据量,别等到外层再筛 - 别用
OFFSET 10 LIMIT 10替代——当id有删改时,OFFSET 会跳过实际行数,而 ROW_NUMBER 分页基于逻辑序号,更稳定 - 如果排序字段允许 NULL,记得加
ORDER BY COALESCE(sort_col, '1970-01-01')避免 NULL 排最前/最后导致分页错位
MySQL 8.0+ 和 PostgreSQL 可用 CTE 提升可读性,但别滥用
CTE 不是语法糖,它会被优化器展开,性能和子查询基本一致,但写起来更清晰。不过要注意:SQL Server 的 CTE 有递归默认限制(100层),虽然分页用不到递归,但若后续叠加其他 CTE 容易触雷。
PostgreSQL 示例:
WITH numbered AS ( SELECT id, amount, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM payments WHERE amount > 0 ) SELECT id, amount FROM numbered WHERE rn BETWEEN 21 AND 30;
关键提醒:
- CTE 名字不能和真实表名冲突,否则某些版本 PostgreSQL 会优先解析为表,报
column "rn" does not exist - MySQL 8.0 对 CTE 的物化策略较激进,大数据集下可能临时落盘,反而比内联子查询慢一点
- 别在 CTE 里写
SELECT *再在外层挑字段——多传无用列会增加内存和网络开销
分页越靠后,性能衰减越明显,必须加覆盖索引
查第10000页(BETWEEN 100001 AND 100010)时,数据库仍要算出前100010行的 ROW_NUMBER,再丢弃前100000行。这和 OFFSET 类似,本质是“跳过式扫描”。
能缓解的只有索引:
- 复合索引要包含
WHERE条件字段 +ORDER BY字段,例如INDEX(status, id) - 如果只查几列(如
id, title),把它们全包含进索引,让查询变成索引覆盖,避免回表 - 别依赖主键索引自动覆盖——
WHERE status = ?无法走PRIMARY KEY(id),必须显式建联合索引
没有索引支撑的 ROW_NUMBER 分页,在百万级表上查末尾页可能卡住十几秒,且无法通过加机器解决。

















