GROUP BY 慢主要因未走索引触发临时表和文件排序;需按最左前缀建联合索引,确保SELECT非聚合列在索引左侧,避免SELECT *,控制分组基数,并调优tmp_table_size等参数。

为什么 GROUP BY 会慢?先看执行计划里的 key 字段
MySQL 执行 GROUP BY 时,默认可能走不了索引,尤其当 SELECT 列和 GROUP BY 列不一致、或存在 HAVING 过滤、或用了非前缀字段排序时,容易触发临时表(Using temporary)和文件排序(Using filesort)。这两个标志一出现,基本就说明性能有隐患。
用 EXPLAIN 查看执行计划,重点盯紧 type(是否为 ref/range)、key(是否命中索引)、Extra(是否含 Using temporary 或 Using filesort)。
给 GROUP BY 字段建联合索引的三个硬规则
不是随便加个索引就行。MySQL 的 GROUP BY 优化依赖「最左前缀 + 排序友好」,必须同时满足:
- 索引顺序必须严格匹配
GROUP BY字段顺序(例如GROUP BY a, b,索引得是(a, b),不能是(b, a)) - 如果带
ORDER BY,且字段顺序/方向与GROUP BY一致(如GROUP BY a, b ORDER BY a, b),可复用同一索引;若方向相反(如ORDER BY a DESC, b ASC),MySQL 8.0+ 才支持降序索引,5.7 及以前会退化为文件排序 -
SELECT中的非聚合列(如SELECT a, b, MAX(c)中的a, b)必须包含在索引最左侧,否则仍需回表,甚至放弃索引
示例:对 SELECT dept_id, COUNT(*) FROM user GROUP BY dept_id,建索引 ALTER TABLE user ADD INDEX idx_dept (dept_id) 即可;但若写成 SELECT dept_id, name, COUNT(*) ... GROUP BY dept_id,而 name 不在索引里,就可能失效。
避免 SELECT * 和多余聚合字段拖慢 GROUP BY
SELECT * 是 GROUP BY 查询的大忌——它强制 MySQL 先分组再回表取所有列,极大增加 I/O 和内存开销。更隐蔽的问题是「隐式 ANY_VALUE 行为」:开启 sql_mode=ONLY_FULL_GROUP_BY(默认开启)时,MySQL 会报错;关掉它虽能运行,但结果不可控。
实操建议:
- 只
SELECT明确需要的分组字段和聚合函数,如SELECT dept_id, COUNT(*), AVG(salary) - 若真要带其他字段(如每个部门的「最早入职员工姓名」),改用
ROW_NUMBER()窗口函数(MySQL 8.0+)或关联子查询,别硬塞进GROUP BY - 检查
GROUP BY字段基数:如果dept_id只有 5 个值,但表有千万行,分组本身快;但如果GROUP BY order_time(精确到秒),分组键太多,临时表膨胀,此时应考虑按天/小时聚合预计算
临时表和排序参数调优的临界点
当无法避免临时表时,至少不让它落到磁盘上。关键参数是 tmp_table_size 和 max_heap_table_size,二者取小值决定内存临时表上限。若分组结果集超过该值,MySQL 会转用 MyISAM 临时表(磁盘),性能断崖下跌。
查当前设置:SHOW VARIABLES LIKE 'tmp_table_size';,生产环境建议设为 64M~256M(根据可用内存按比例分配);同时确认 max_heap_table_size 不低于该值。
另一个易忽略点:sort_buffer_size 影响 GROUP BY 内部排序效率,但它 per-thread 分配,不宜设过大(如 > 4M),否则并发高时内存爆炸。
真正卡顿的往往不是 SQL 写法,而是分组键分布极度倾斜(比如 99% 数据集中在某 1 个 dept_id),这时单个分组处理时间远超平均值,监控 Performance Schema 的 events_statements_summary_by_digest 能定位这类长尾查询。










