group by 用临时表是因为缺乏合适索引,无法按分组字段顺序读取数据,被迫先写入内存临时表再排序聚合;一旦超 tmp_table_size 与 max_heap_table_size 较小值即落盘,引发 i/o 性能断崖。

为什么 GROUP BY 会用临时表?
看到 EXPLAIN 的 Extra 列里出现 Using temporary,就说明 MySQL 没法靠索引直接分组,被迫把所有匹配行先写进内存临时表(MEMORY 引擎),再排序、去重、聚合。一旦数据量超过 tmp_table_size 和 max_heap_table_size 中的较小值,就会落盘到磁盘临时表(MyISAM 或 InnoDB),I/O 开销暴涨。
建什么索引才能让 GROUP BY 跳过临时表?
关键不是“有没有索引”,而是索引能否让 MySQL 顺着索引顺序读,使相同分组值天然连续。这要求:
- 索引字段顺序必须严格匹配
GROUP BY子句的字段顺序(例如GROUP BY dept, role→ 索引必须是(dept, role),反过来无效) - 如果带
WHERE等值条件(如status = 'active'),要把该字段放最左,再接分组字段((status, dept, role)) - 如果
SELECT中有非聚合列(如SELECT dept, MAX(salary), name),name必须也加入索引末尾,否则会回表,甚至让优化器弃用该索引 - 避免在分组字段上用函数或表达式(如
GROUP BY YEAR(created_at)),这会让索引失效
ORDER BY NULL 真的能关掉文件排序吗?
能,而且必须加。MySQL 5.7+ 默认对 GROUP BY 结果隐式按分组字段升序排序,哪怕你没写 ORDER BY。这个行为会触发 Using filesort —— 它和临时表是两回事,但常一起出现。
加了 ORDER BY NULL 后,只要索引结构正确,Extra 会变成空或仅 Using index。不加的话,即使索引完美,排序开销也可能远超聚合本身。
tmp_table_size 和 sort_buffer_size 到底该调谁?
sort_buffer_size 对纯 GROUP BY 几乎没用:它只在显式 ORDER BY 或某些带排序的 GROUP BY 场景下分配,且每个连接独占一份,调大不解决 Using temporary。
真正控制临时表是否落盘的是 tmp_table_size 和 max_heap_table_size(二者取小值)。若确认必须走临时表(比如分组逻辑太复杂),可同步调大这两个值(如设为 67108864,即 64MB),但这是兜底手段,不能替代索引。
临时表逻辑比表面看起来更微妙:是否启用松散索引扫描(Loose Index Scan)、NULL 值处理、ONLY_FULL_GROUP_BY 模式,都会影响最终执行路径——所以别只盯着参数,先盯紧 EXPLAIN 里的 type、key 和 Extra。











