rank()、dense_rank()和row_number()核心区别在于相同值的编号逻辑:row_number()严格递增(1,2,3),rank()并列跳号(1,1,3),dense_rank()并列不跳号(1,1,2)。

RANK()、DENSE_RANK() 和 ROW_NUMBER() 三者语义不同,选错函数会导致排名结果不符合业务预期——比如“年级前10名”用 RANK() 可能返回12人,“班级内唯一序号”却误用了 DENSE_RANK()。
区分 RANK()、DENSE_RANK() 和 ROW_NUMBER() 的实际行为
三者都必须配合 OVER(ORDER BY ...) 使用,但对相同值的处理逻辑完全不同:
-
ROW_NUMBER():严格递增,不管值是否重复(95,95,90→1,2,3) -
RANK():并列同名次,后续跳号(95,95,90→1,1,3) -
DENSE_RANK():并列同名次,后续不跳号(95,95,90→1,1,2)
常见错误是把 DENSE_RANK() 当成“去重排名”,其实它只是不跳号;而误用 RANK() 做 Top N 限制(如 WHERE rank_num ),可能漏掉并列第10名之后的实际第10名。
PARTITION BY 漏写就等于没分组
想查“每个部门薪资前三”,却只写 RANK() OVER (ORDER BY salary DESC),结果是全表统一排名——所有员工混在一起排,不是按部门各自排。
正确写法必须显式声明分区:
SELECT dept, name, salary,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_rank
FROM employees;
注意:PARTITION BY 不会自动继承 GROUP BY 或查询外层的分组逻辑;它只认 OVER() 里写的字段。如果分区字段含 NULL,MySQL 会把所有 NULL 归为同一组,必要时加 WHERE dept IS NOT NULL 过滤。
ORDER BY 缺失或 NULL 处理不当直接报错或出错
不写 ORDER BY 会报错:Window 'w' lacks an ORDER BY clause;写了但字段含大量 NULL,又没控制顺序,就容易让一堆 NULL 全占了前几名。
推荐做法:
- 优先在
WHERE阶段过滤:WHERE score IS NOT NULL(比在窗口里处理更高效) - 若需保留 NULL 行,显式指定位置:
ORDER BY score DESC NULLS LAST(MySQL 8.0.22+ 支持) - 低版本兼容写法:
ORDER BY IF(score IS NULL, 1, 0), score DESC
别依赖默认行为——不同 SQL 模式下,NULL 排序结果可能不一致,线上环境务必显式控制。
性能瓶颈往往藏在 ORDER BY 字段没索引
窗口函数本身不慢,慢的是背后触发的 filesort。如果 ORDER BY 字段没索引,百万级数据就会明显卡顿。
验证方式:
EXPLAIN SELECT name, score, RANK() OVER (ORDER BY unindexed_col DESC) FROM students;
关注 Extra 列是否出现 Using filesort 或 Using temporary。解决方法很简单:给排序字段建索引,例如 ALTER TABLE students ADD INDEX idx_score (score);。
另外,PARTITION BY 字段也建议有索引,尤其当分区键选择性差(如只有几个部门值)时,索引能加速分组切片过程。











