根本区别在于并列后是否跳号:RANK()并列后跳过后续名次(如两个第1名后是第3名),DENSE_RANK()则不跳(两个第1名后是第2名),直接影响排行榜是否存在空档位。

为什么 RANK() 和 DENSE_RANK() 返回的排名看起来一样但实际不同
根本区别在「并列后是否跳号」:当有重复值时,RANK() 会跳过后续名次(比如两个第1名后直接是第3名),而 DENSE_RANK() 不跳(两个第1名后是第2名)。这直接影响业务语义——比如排行榜是否允许“空档位”。常见错误是没注意排序字段存在重复值,结果发现排名数字断层却查不出原因。
实操建议:
- 先用
ORDER BY明确排序依据,且该字段最好有索引(如score或created_at) - 若需稳定排名(相同分数下按时间再排),必须在
ORDER BY中补充二级排序字段,例如:ORDER BY score DESC, created_at ASC - MySQL 8.0+ 才支持窗口函数;5.7 或更低版本执行会报错
ERROR 1064 (42000),别在旧环境硬试
怎么写一个带分组的排名(比如每个班级内学生按成绩排名)
关键在 PARTITION BY。它不是简单加个 GROUP BY,而是把数据逻辑切片后,在每片内独立计算排名。漏写或写错分区字段,会导致全表统一排名,完全偏离预期。
实操建议:
-
PARTITION BY class_id表示按班级分组,每个班级内单独跑RANK() - 分区字段值为
NULL的行会被归入同一组,可能造成意外聚合,建议提前WHERE class_id IS NOT NULL - 避免在
PARTITION BY中使用表达式(如PARTITION BY YEAR(created_at)),部分 MySQL 版本不支持,优先用已存在的列
示例:
SELECT name, class_id, score, RANK() OVER (PARTITION BY class_id ORDER BY score DESC) AS rank_in_class FROM students;
ROW_NUMBER()、RANK()、DENSE_RANK() 三者性能和适用场景差异
三者底层都走窗口计算,单次查询性能差异微乎其微,瓶颈通常在排序本身。真正影响选型的是业务规则:是否允许并列、并列后是否占位。
实操建议:
- 严格唯一序号(如发奖编号、流水号)→ 用
ROW_NUMBER() - 允许并列但要体现“名次含金量”(比如两个第1名意味着都压过了第2名)→ 用
RANK() - 并列即同名次,且后续名次紧连(如榜单展示、用户等级)→ 用
DENSE_RANK() - 所有函数都不能在
WHERE或HAVING中直接引用(因窗口函数执行晚于 WHERE),要取 top 3 必须套一层子查询或 CTE
为什么在 ORDER BY 后加 LIMIT 会导致排名异常
因为 LIMIT 在窗口函数之后执行,如果先 LIMIT 10 再算排名,就只对这10行排名,而不是全表排完再取前10。结果就是你看到的“第1名”可能其实是全表第100名。
实操建议:
- 想取每个班级前3名?必须把窗口函数放在子查询里,外层再
WHERE rank_in_class - MySQL 8.0 支持 CTE,更清晰:
WITH ranked AS (SELECT ..., RANK() OVER (...) r FROM t) SELECT * FROM ranked WHERE r - 别依赖客户端做截断,数据库层控制才是可靠方式
最容易被忽略的一点:窗口函数的 ORDER BY 子句中,如果排序字段有大量重复值,又没加二级排序,MySQL 可能每次执行返回不同顺序——导致 ROW_NUMBER() 结果不稳定,RANK() 和 DENSE_RANK() 虽然名次一致,但具体哪行排第几仍可能漂移。


















