group by + limit 更慢,因为group by必须先完成全部分组聚合并生成完整结果集,limit仅在其后截取,无法减少分组开销;执行时易触发using temporary和using filesort,表明数据库已全表扫描并依赖临时表或磁盘排序。

GROUP BY + LIMIT 为什么比单纯 LIMIT 更慢
因为 GROUP BY 必须先完成全部分组聚合,才能生成结果集;而 LIMIT 只是在这个完整结果集上截取,它不参与、也不减少分组过程的任何开销。换句话说,数据库得先把几百万行按 GROUP BY 字段全分好组、算完 COUNT(*) 或 AVG(),再对这个中间结果排序(哪怕没写 ORDER BY),最后才 LIMIT。整个流程绕不开临时表或磁盘排序。
执行计划里最该盯住的两个关键词
查 EXPLAIN ANALYZE 时,重点看是否出现:
-
Using temporary:MySQL 正在用内存或磁盘临时表做分组 -
Using filesort:即使没写ORDER BY,GROUP BY 输出顺序不确定,优化器可能额外加一次排序
这两个标志一齐出现,基本就坐实了性能瓶颈在分组阶段本身,而不是后续的 LIMIT。
常见但危险的“隔离 LIMIT”写法
有人把 GROUP BY 套进子查询,外层再 LIMIT,比如:
SELECT * FROM ( SELECT dept, COUNT(*) c FROM emp GROUP BY dept ) t LIMIT 10;
这看似“隔离”,实则无效——子查询仍需全量分组,LIMIT 没起到任何剪枝作用。真正有效的隔离是:
- 先用
WHERE大幅过滤数据(如时间范围、状态字段),再GROUP BY - 或用覆盖索引只查主键,再关联聚合(类似深度分页的延迟关联思路)
- 若只需 TopN 分组,可加
HAVING COUNT(*) > X提前筛掉小分组
索引建错一个位置,性能就差十倍
GROUP BY a, b 要走索引,必须建 (a, b) 复合索引,且顺序不能颠倒;如果还有 WHERE c = ?,索引应为 (c, a, b),让过滤和分组共用一棵索引树。容易踩的坑包括:
- 只建了单列
a索引,GROUP BY a, b依然会Using temporary - 在
GROUP BY字段上用了函数,如GROUP BY DATE(created_at),索引直接失效 -
SELECT *导致无法索引覆盖,强制回表,放大 I/O
真正难处理的,从来不是 LIMIT 本身,而是你没意识到:GROUP BY 那一步,数据库早就把整张表翻过一遍了。











