不能直接用 GROUP BY 配合 LIMIT,因为 GROUP BY 不保留组内顺序且 LIMIT 作用于最终结果而非每组;应使用 ROW_NUMBER() 窗口函数配合 PARTITION BY 和 ORDER BY 编号后过滤。

为什么不能直接用 GROUP BY 配合 LIMIT
因为 GROUP BY 本身不保留组内行序,LIMIT 也无法作用于每个分组。你写 SELECT * FROM t GROUP BY category ORDER BY score DESC LIMIT 3,结果是整张表取前三,不是每组前三。
用 ROW_NUMBER() 按分组排序后过滤
这是最常用也最可控的方式:先用窗口函数给每组内的记录编号,再在外面筛选编号 ≤ 3 的行。
关键点:
-
ROW_NUMBER()必须配合OVER (PARTITION BY ... ORDER BY ...)使用,缺一不可 -
PARTITION BY决定“分组依据”,ORDER BY决定组内排序逻辑(比如score DESC) - 不能在同一个查询层级里对窗口函数结果做条件过滤(即不能在
WHERE中写ROW_NUMBER() ),必须用子查询或 CTE
SELECT category, name, score
FROM (
SELECT category, name, score,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS rn
FROM scores
) ranked
WHERE rn <= 3;RANK() 和 DENSE_RANK() 的区别在哪
当存在并列分数时,三者行为不同:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
ROW_NUMBER():严格按顺序编号,相同分数也会分出 1/2/3(比如 95, 95, 90 → 1, 2, 3) -
RANK():并列名次相同,但会跳过后续编号(95, 95, 90 → 1, 1, 3) -
DENSE_RANK():并列名次相同,不跳号(95, 95, 90 → 1, 1, 2)
如果你要“取分数前三高的所有选手”,且允许并列,RANK() 更合适;如果明确只要三条记录,哪怕并列也要硬卡数量,就用 ROW_NUMBER()。
性能和索引注意事项
窗口函数本身不走索引,但 PARTITION BY 和 ORDER BY 字段的组合如果有联合索引,能显著加速排序过程。
- 推荐建索引:
ALTER TABLE scores ADD INDEX idx_cat_score (category, score DESC); - MySQL 8.0 对
ORDER BY ... DESC在索引中支持良好,但老版本可能忽略DESC定义 - 如果数据量极大(千万级以上),且只查少数几个 category,可先用
WHERE category IN (...)过滤再开窗,避免全表扫描
别忘了检查执行计划:EXPLAIN FORMAT=TREE 能看到是否用了索引以及是否触发了 filesort。

















