DENSE_RANK()更适合“取前三名”,因其对并列值排名连续不跳号(如1、1、2、2、3),准确对应业务语义;而ROW_NUMBER()强行去重(1、2、3),RANK()跳号(1、1、3),均可能导致遗漏或错选。

为什么 DENSE_RANK() 比 RANK() 和 ROW_NUMBER() 更适合“取前三名”
因为你要的是“分组内排名前三的记录”,而不是“前三条记录”。当存在并列(比如两个用户同为 95 分),ROW_NUMBER() 会强行拆成 1、2、3,漏掉第二个 95 分;RANK() 则跳号(1、1、3),导致“第三名”实际可能对应排名值为 4 的记录;而 DENSE_RANK() 是 1、1、2、2、3 —— 名次连续、不跳空、保留并列,正好对应业务中常说的“前三名”语义。
实操建议:
- 始终在
OVER子句中明确写全PARTITION BY(分组字段)和ORDER BY(排序字段),漏掉PARTITION BY就变成全表排名 - 排序方向必须与业务一致:比如要“分数最高的前三名”,
ORDER BY score DESC;反向会拿到最低分的三条 - MySQL 8.0+ 才支持窗口函数,执行
SELECT VERSION();确认版本,低于 8.0 会报错FUNCTION xxx does not exist
怎么写 SQL 才能真正只取每组前三条
窗口函数不能直接在 WHERE 中引用别名(比如 WHERE rk 会报错 Unknown column 'rk'),必须用派生表或 CTE 包一层。
推荐用 CTE(更可读):
WITH ranked AS (
SELECT
user_id,
category,
score,
DENSE_RANK() OVER (PARTITION BY category ORDER BY score DESC) AS rk
FROM scores
)
SELECT user_id, category, score
FROM ranked
WHERE rk
<p>常见错误:</p>
- 把
DENSE_RANK()放在WHERE或HAVING里 —— 语法不合法 -
PARTITION BY字段值含NULL:MySQL 会把所有NULL归为同一组,若业务上category IS NULL是无效数据,先WHERE category IS NOT NULL - 没加
ORDER BY:窗口函数允许省略,但结果不可预测,MySQL 不保证默认顺序
遇到并列太多时,如何控制最终返回行数
DENSE_RANK() = 3 可能对应 5 条记录(比如第 3 名有 3 人并列,加上前面两个并列第 1 名),这属于正常行为 —— “前三名”本就可能超过三行。如果硬性要求最多 3 行/组,得换策略。
此时应改用 ROW_NUMBER(),但它只解决“取前 N 行”,不反映真实名次:
WITH numbered AS (
SELECT
user_id,
category,
score,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC, user_id ASC) AS rn
FROM scores
)
SELECT user_id, category, score
FROM numbered
WHERE rn
<p>注意点:</p>
- 必须加二级排序(如
user_id ASC)来打破完全并列,否则ROW_NUMBER()结果不稳定(多次执行可能不同) - 不要误以为
ROW_NUMBER()能替代DENSE_RANK()—— 它给出的是序号,不是名次 - 若既要真实名次、又要限总行数,只能先
DENSE_RANK(),再对rk 的结果做额外截断(比如用变量或应用层处理)
性能和索引怎么配才不慢
窗口函数本身不走索引,但 PARTITION BY + ORDER BY 字段组合是优化关键。MySQL 8.0 对窗口函数的执行计划显示为 Using filesort,不代表一定慢 —— 如果内存够,就是内存排序。
加速建议:
- 建联合索引:
CREATE INDEX idx_cat_score ON scores(category, score DESC);—— 注意DESC在 MySQL 8.0+ 支持,且必须和ORDER BY方向一致 - 避免在
SELECT中对大字段(如TEXT)做窗口计算,先过滤再计算 - 如果分组数量极大(如百万级不同
category),DENSE_RANK()会逐组排序,内存压力明显,考虑加LIMIT或分批查
最易被忽略的一点:窗口函数的排序是在整个分组内做的,哪怕你只想要前三名,MySQL 仍会把该组所有行拉进来排序 —— 没有“提前终止”机制。所以分组大小直接影响响应时间。











