MySQL 8.0+支持ROW_NUMBER()、RANK()、DENSE_RANK()等窗口函数,排序稳定可靠;5.7及之前需用变量或自连接,但变量依赖ORDER BY且不保证执行顺序,自连接性能差(O(n²)),均不推荐用于生产环境。

MySQL 8.0+ 直接用 ROW_NUMBER()、RANK()、DENSE_RANK() 就行;5.7 或更早版本必须靠变量或自连接,且结果不稳定 —— 别硬套 8.0 文档去跑老版本。
MySQL 8.0+:直接用窗口函数,最稳
窗口函数是唯一能保证排序逻辑与执行顺序一致的方案。只要表有明确排序依据(比如按分数降序),就能可靠生成排名。
-
ROW_NUMBER():严格递增,相同值也不同序号(1,2,3,4) -
RANK():跳号处理,相同值同名次(1,2,2,4) -
DENSE_RANK():不跳号,相同值同名次(1,2,2,3)
示例:按 score 降序排班级名次
SELECT name, score,
RANK() OVER (ORDER BY score DESC) AS rank_num
FROM students;注意:OVER 子句里不能用子查询或用户变量;排序字段最好加索引,否则 ORDER BY 在窗口内会触发临时文件排序。
MySQL 5.7 及之前:用变量模拟,但必须加 ORDER BY 强制排序
变量赋值依赖执行顺序,而 MySQL 不保证 SELECT 中表达式求值顺序 —— 所以没 ORDER BY 的变量排名大概率错乱。
- 必须在同一个
SELECT中完成变量初始化和递增,不能分步 - 变量声明要写在最外层
SELECT前,如@rank := 0 - 排序必须显式写在
ORDER BY,且不能被优化器“优化掉”(例如关联后没强制排序)
正确写法:
SELECT name, score,
@rank := @rank + 1 AS rank_num
FROM students,
(SELECT @rank := 0) r
ORDER BY score DESC;错误写法(无 ORDER BY 或放在子查询里)会导致排名随机;另外,如果 score 有重复,这种写法无法实现 RANK() 的并列逻辑,得额外加判断。
避免用自连接计算排名,尤其大数据量时
原理是统计“比当前记录分数高或相等的记录数”,理论上能兼容所有版本,但性能极差。
- 时间复杂度 O(n²),1 万行就可能秒变几秒甚至超时
- 无法利用索引加速计数(
COUNT(*)配合WHERE条件很难走索引) - 若排序字段允许 NULL,需额外处理 NULL 比较逻辑(默认 NULL 最小还是最大?)
示例(仅用于小表验证):
SELECT a.name, a.score,
COUNT(b.score) + 1 AS rank_num
FROM students a
LEFT JOIN students b ON b.score > a.score
GROUP BY a.name, a.score
ORDER BY a.score DESC;实际生产环境遇到几百行以上就该换方案了。
排名结果需要分页或带条件筛选?先算排名再过滤
窗口函数可在子查询或 CTE 中先算好排名,再对外层加 WHERE;变量方案则必须把排序+排名逻辑包进子查询,否则 LIMIT 会截断未排序数据。
- 错误:先
LIMIT再排名 → 排的是局部数据,不是全局名次 - 正确:子查询里完成排名,外层再
WHERE rank_num BETWEEN 11 AND 20 - CTE 更清晰(8.0+):
WITH ranked AS (SELECT ..., RANK() OVER (...) AS r) SELECT * FROM ranked WHERE r
变量方案若漏掉外层包装,LIMIT 10 可能返回第 1–10 名以外的任意 10 行 —— 这个坑特别隐蔽,查半天才发现没排序就截断了。


















