ROW_NUMBER()、RANK()和DENSE_RANK()在MySQL 8.0中必须配合含ORDER BY的OVER()子句使用,否则报错;三者对并列值处理不同:ROW_NUMBER()严格递增,RANK()并列跳号,DENSE_RANK()并列不跳号;PARTITION BY控制分组边界,ORDER BY列建议建联合索引以提升性能。

ROW_NUMBER()、RANK() 和 DENSE_RANK() 在 MySQL 8.0 中必须配合 OVER() 子句使用,缺一不可;单独写会报错 ERROR 1064。
ROW_NUMBER() 必须带 ORDER BY,否则语法不合法
MySQL 要求所有排序类窗口函数的 OVER() 中必须包含 ORDER BY,哪怕你只是想按插入顺序编号。不写会直接报错:
ERROR 3593: Window function 'row_number' requires an OVER clause with ORDER BY
常见误用是只写 PARTITION BY 而漏掉 ORDER BY,比如想给每个班级学生编号却不指定排序依据:
❌ 错误写法:ROW_NUMBER() OVER (PARTITION BY class)
✅ 正确写法(按学号升序):ROW_NUMBER() OVER (PARTITION BY class ORDER BY student_id)
✅ 正确写法(按插入顺序模拟,需有自增主键或时间戳):ROW_NUMBER() OVER (PARTITION BY class ORDER BY id)
注意:ORDER BY 是强制的,但可以是任意列——包括常量(如 ORDER BY 1),不过这会导致结果不稳定,不建议生产环境使用。
RANK() 和 DENSE_RANK() 的并列处理逻辑差异很关键
三者面对相同值时的行为完全不同,直接影响业务含义:
-
ROW_NUMBER():严格递增,相同分数也给不同序号(1,2,3,4) -
RANK():并列则同名次,但跳过后续名次(1,1,3,4) -
DENSE_RANK():并列则同名次,不跳后续(1,1,2,3)
例如成绩为 [95,95,90,90,85] 时:
ROW_NUMBER() → 1,2,3,4,5
RANK() → 1,1,3,3,5
DENSE_RANK() → 1,1,2,2,3
选错函数会导致教务系统排名公示出错,尤其是涉及奖学金名额(按前 N 名)时:RANK() 下“第 3 名”可能实际有两人,而 DENSE_RANK() 下“第 3 名”一定是唯一一人。
PARTITION BY + ORDER BY 组合决定计算边界和稳定性
PARTITION BY 不是可选项,而是控制“谁跟谁比”的核心。漏掉它,就变成全表统一排序,不是分区排名。
典型错误场景:
- 想查“每个班级数学最高分的学生”,却没加
PARTITION BY class,结果只返回全校第一,而非每班第一 - 用
ORDER BY score DESC但没加PARTITION BY,导致ROW_NUMBER()把全校学生混排,无法定位各班内部序号
另外,ORDER BY 列如果有重复值,且未补充二级排序,结果可能非确定性(尤其在并发写入时)。建议补一个唯一列兜底:
✅ 推荐写法:ROW_NUMBER() OVER (PARTITION BY class ORDER BY score DESC, student_id)
这样即使分数相同,也能靠 student_id 确保每次执行结果一致。
性能上,ORDER BY 列最好有索引
窗口函数的 ORDER BY 会触发排序操作。如果对应列没有索引,大表查询可能明显变慢,甚至触发磁盘临时表。
比如执行:SELECT *, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) FROM employees;
若 (department, salary) 没有联合索引,MySQL 会为每个 department 分区单独排序,开销随分区数线性增长。
优化建议:
- 为
PARTITION BY+ORDER BY的组合列建联合索引,顺序要匹配(先PARTITION BY列,再ORDER BY列) - 避免在
ORDER BY中使用函数或表达式,如ORDER BY UPPER(name),会导致索引失效
最易被忽略的一点:窗口函数本身不支持 LIMIT 下推,所以如果只想取每个分区的 Top 3,不能只靠 LIMIT 3,必须用子查询或 CTE 过滤 ROW_NUMBER() —— 否则 <code>LIMIT 作用于最终结果,不是每个分区。


















