ROW_NUMBER() 生成唯一连续序号,重复值仍递增编号;RANK() 实现并列排名,重复值名次相同且后续名次跳过。选哪个取决于业务需求是“唯一序号”还是“真实名次”。

ROW_NUMBER() 和 RANK() 的结果差异,根本不在写法,而在排序逻辑
很多人写对了语法却得不出预期结果,是因为没意识到:两者对重复值的处理是设计上的根本分歧,不是 bug,而是 feature。
-
ROW_NUMBER()只看行顺序,不管值是否相同——哪怕两行salary都是 25000,也会编成 1 和 2 -
RANK()模拟真实排名规则:值相同则名次并列,下一个名次跳过已用掉的编号(比如两个第 1 名,下一个是第 3 名) - 举个例子:
SELECT score, ROW_NUMBER() OVER (ORDER BY score DESC), RANK() OVER (ORDER BY score DESC)对数据[90, 85, 85, 80]的输出分别是[1,2,3,4]和[1,2,2,4]
选哪个?取决于你到底要“序号”还是“名次”
这不是风格偏好问题,而是业务语义是否匹配。用错一个,整个分析逻辑就偏了。
- 要取每个部门最新的一条订单记录?用
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY create_time DESC),然后WHERE rn = 1—— 这里必须唯一,不能容忍并列 - 要做销售业绩排行榜,允许多人并列第一?用
RANK() OVER (ORDER BY amount DESC),再WHERE rk 才可能返回 5 条记录(比如两人并列第 1,一人第 3) - 如果误用
ROW_NUMBER()实现“前三名”,实际只拿到三条,漏掉并列者——这是面试和线上事故高频原因
常见踩坑点:别名不能直接在 WHERE 里用,PARTITION BY 写错就全乱了
窗口函数是在 SELECT 阶段计算的,而 WHERE 是在它之前执行的,所以别名在同级 WHERE 中不可见;另外分组维度错一点,排名就完全失真。
- 错误写法:
SELECT *, RANK() OVER (ORDER BY score) AS rk FROM scores WHERE rk → 报错或逻辑错误 - 正确写法:必须套一层子查询或 CTE,外部再过滤:
SELECT * FROM (SELECT *, RANK() OVER (...) AS rk FROM scores) t WHERE t.rk -
PARTITION BY dept_id写成PARTITION BY city,会导致“每个城市的员工在部门内排名”这种荒谬结果——检查业务口径和字段含义比调 SQL 更重要
MySQL 8.0+ 性能提示:没特殊需求时,优先用 ROW_NUMBER()
在千万级数据量实测中,ROW_NUMBER() 稳定比 RANK() 快 10%~15%,因为后者需要额外扫描重复块来确定跳号位置。
- 如果只是分页、去重、取 Top N(不要求并列),
ROW_NUMBER()更轻量也更安全 -
RANK()和DENSE_RANK()在高并发 OLAP 场景下,可能因内部排序缓存策略导致波动,上线前建议压测对比 - 注意:所有这三个函数都要求 MySQL ≥ 8.0,低版本需用变量模拟,但变量方案在多线程/并行查询下极易出错
真正卡住人的,从来不是怎么写 OVER 子句,而是想清楚——你要的是“第几个被读到的”,还是“第几名”。










