子查询算排名又慢又错,因内层COUNT(*)无法用索引、每次全表扫描,且同分跳名次;应改用DENSE_RANK()等窗口函数,性能好、语义准、支持连续排名。

MySQL 8.0里用子查询算排名为什么又慢又错?
MySQL 8.0之前,很多人靠关联子查询或自连接实现“第几名”逻辑,比如:
SELECT a.name, a.score, (SELECT COUNT(*) FROM scores b WHERE b.score > a.score) + 1 AS rank FROM scores a ORDER BY a.score DESC;
这种写法在数据量稍大(比如超5千行)时就明显变慢,而且遇到相同分数时会跳名次(如 1,2,2,4),不是严格连续排名。更关键的是:它无法利用索引加速内层 COUNT(*),每次都要全表扫一遍 b 表。
用 RANK()、DENSE_RANK() 和 ROW_NUMBER() 直接替代
窗口函数是真正解决这个问题的方案,语法简洁、性能好、语义明确。三者区别直接影响排名结果:
-
RANK():同分同名,跳过后续名次(1,2,2,4) -
DENSE_RANK():同分同名,不跳名次(1,2,2,3) -
ROW_NUMBER():强制唯一序号,同分也不同名(1,2,3,4)
例如按分数降序排连续名次:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
SELECT name, score, DENSE_RANK() OVER (ORDER BY score DESC) AS rank FROM scores;
注意 OVER 子句必须存在,且不能省略括号;排序方向(DESC)不写默认是 ASC,和业务直觉相反,容易漏改。
WHERE 里不能直接用窗口函数别名
这是新手最常踩的坑:
WHERE










