基础排行榜用ORDER BY+LIMIT实现,关键在避免全表扫描和应对高并发;查个人排名需异步缓存或窗口函数配合覆盖索引;并列排名用RANK()或DENSE_RANK(),索引设计决定性能上限。

用 ORDER BY + LIMIT 实现基础排行榜
直接按分数倒序取前 N 条,是绝大多数排行榜的起点。关键不是“能不能做”,而是“怎么避免查全表”和“如何应对高并发读”。
-
SELECT user_id, score FROM rankings ORDER BY score DESC LIMIT 100这条语句在score字段有索引时效率很高;没索引则会触发Using filesort,慢得明显 - 如果用户需要“查自己排名”,不能只靠
LIMIT,得额外算位置——这时候别用ROW_NUMBER()在大表上实时计算,开销太大 - 注意
score相同时的排序稳定性:建议加上user_id作为第二排序字段(ORDER BY score DESC, user_id ASC),避免分页错位
用子查询或变量模拟排名序号(兼容 MySQL 5.7)
MySQL 5.7 不支持窗口函数,但排行榜常需返回“第几名”。用变量方式能绕过,但必须严格控制执行顺序,否则序号错乱。
- 写法示例:
SELECT rank, user_id, score FROM ( SELECT @rank := @rank + 1 AS rank, user_id, score FROM rankings, (SELECT @rank := 0) r ORDER BY score DESC, user_id ASC ) t WHERE user_id = 123;
- ⚠️ 严重陷阱:
@rank变量赋值顺序依赖ORDER BY实际执行路径,MySQL 8.0+ 已不推荐;且该语句无法走索引覆盖,大数据量下性能骤降 - 更稳妥的做法:把排名结果异步写入一张
rank_cache表,定时更新(比如每分钟一次),查榜时只读这张缓存表
MySQL 8.0+ 推荐用 ROW_NUMBER() + 覆盖索引
有了窗口函数,逻辑清晰了,但性能仍取决于索引设计和数据分布。
- 正确写法:
SELECT user_id, score, ROW_NUMBER() OVER (ORDER BY score DESC, user_id ASC) AS rank FROM rankings WHERE score >= 900;
加WHERE条件可大幅减少计算量 - 必须建联合索引:
CREATE INDEX idx_score_uid ON rankings(score DESC, user_id ASC),否则OVER子句仍要扫描全表 - 注意:如果只查 Top 100,别对全表跑
ROW_NUMBER()再LIMIT——先LIMIT再算排名,或改用物化视图思路
处理并列排名(同分同名次)用 RANK() 而非 ROW_NUMBER()
游戏、答题类场景常要求“95 分并列第 3 名,下一个 94 分是第 5 名”。这和 ROW_NUMBER() 的严格递增完全不同。
-
RANK()会跳过后续名次,DENSE_RANK()则连续(95 分第 3,94 分第 4)——选哪个取决于业务规则 - 示例:
SELECT user_id, score, RANK() OVER (ORDER BY score DESC) AS rank FROM rankings;
注意这里没加user_id,因为并列逻辑本就允许相同分数无先后 - 同样依赖索引:即使只用
score排序,也得确保score字段单独有索引,否则排序成本爆炸


















