rank()和row_number()结果不同的根本原因是重复值处理策略不同:rank()对相同值分配相同序号并跳过后续名次,row_number()则为每行分配唯一连续序号。

为什么RANK()和ROW_NUMBER()在组内排名时结果不同
根本原因在于它们对重复值的编号策略不同:当排序字段值相同时,RANK()给相同值分配相同序号,并跳过后续名次;ROW_NUMBER()则无视值是否重复,强制为每一行分配唯一、连续的整数。
比如三个人分数分别是 95, 95, 90:
-
RANK()→1, 1, 3 -
ROW_NUMBER()→1, 2, 3
这不是 bug,而是设计意图。业务上要“并列不占位”,就选 RANK();要“严格一行一号”,必须用 ROW_NUMBER()。
漏写PARTITION BY会导致全表排序而非组内排名
常见错误是只写 ORDER BY,却忘了 PARTITION BY。例如想查每个部门薪资最高的员工,却写了:
SELECT *, RANK() OVER (ORDER BY salary DESC) AS rk FROM employees;
这会把全公司所有人一起排,不是按部门分组排。正确写法必须显式指定分组维度:
-
PARTITION BY dept_id才是按部门切分窗口 - 误把
dept_id放进ORDER BY(如ORDER BY dept_id, salary DESC)会导致每组只有一行——因为dept_id值相同才进同一组,再按它排序毫无意义 - 如果
PARTITION BY字段基数极高(比如用user_id分组),MySQL 内存开销会上升,尤其在大表上需警惕
WHERE子句里不能直接用ROW_NUMBER()或RANK()
下面这句会报错:
SELECT * FROM t WHERE ROW_NUMBER() OVER (PARTITION BY x ORDER BY y) = 1;
因为窗口函数不能出现在 WHERE 中。必须先用 CTE 或子查询算出序号,再在外层筛选:
- 正确写法(CTE):
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn FROM employees ) SELECT * FROM ranked WHERE rn = 1;
- 如果业务只要每个部门“一个代表”,
ROW_NUMBER()+rn = 1是最稳的选择;用RANK()可能返回多个rk = 1的人,还得额外加MIN(id)或LIMIT 1补救 - 别指望靠
RANK()自动去重——它只是编号方式不同,原始行数一点没少
性能差异其实不在函数名,而在你怎么用结果
ROW_NUMBER() 和 RANK() 底层执行计划几乎一样,都依赖排序算子。真正影响快慢的是后续操作:
- 对无索引字段排序(如
ORDER BY SUBSTRING(name, 1, 5))时,排序成本远高于编号逻辑本身 - 用
RANK()后做JOIN或聚合,可能因重复序号导致数据量意外膨胀 - 如果后续要取前 N 名(如
WHERE rk BETWEEN 1 AND 10),RANK()可能漏掉实际第 10 名之后的并列者,而ROW_NUMBER()的范围更可预测 - 索引优化重点在
ORDER BY列,而不是函数名本身;PARTITION BY列是否建索引影响不大,但数据倾斜严重时会影响内存使用
真正容易被忽略的是:你最后一步在干什么。盯紧下游消费逻辑,比纠结函数名字重要得多。











