MySQL GROUP BY 默认先用内存临时表,但当分组数据量超过tmp_table_size与max_heap_table_size的较小值(默认16MB)时,自动转为磁盘临时表(InnoDB引擎);若索引满足松散索引扫描条件(GROUP BY字段为索引最左前缀且无范围WHERE干扰),可完全避免临时表;EXPLAIN中出现Using temporary和Using filesort,通常因未加ORDER BY NULL导致隐式排序。

MySQL GROUP BY 用的是内存临时表还是磁盘临时表?
取决于 tmp_table_size 和实际分组数据量。MySQL 默认先尝试用内存临时表,但一旦中间结果(比如分组键 + 聚合值)超出 tmp_table_size(默认 16MB),就会自动转成磁盘临时表——此时引擎默认用 InnoDB,不是 MyISAM。
常见误判点:看到 Using temporary 就以为一定慢,其实只要数据量小、内存够,全程走内存临时表,开销并不高。
- 可通过
SHOW GLOBAL VARIABLES LIKE 'tmp_table_size';查当前阈值 - 执行后查
SHOW STATUS LIKE 'Created_tmp_%';,Created_tmp_disk_tables增加说明已落盘 - 注意:
max_heap_table_size也参与限制,取二者较小值生效
GROUP BY 什么时候根本不用临时表?
当满足「松散索引扫描」条件时,MySQL 可跳过临时表,直接顺序扫描索引完成分组。核心前提是:GROUP BY 字段是索引的最左前缀,且没有范围 WHERE 条件干扰索引有序性。
例如索引是 (user_id, created_at):
-
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id;→ 可能走松散索引扫描,不建临时表 -
SELECT user_id, COUNT(*) FROM orders WHERE created_at > '2024-01-01' GROUP BY user_id;→ 因范围查询破坏有序性,退化为紧凑索引扫描,仍需临时表或排序 -
SELECT COUNT(*), user_id % 10 FROM orders GROUP BY user_id % 10;→ 表达式无法利用索引,必走临时表
为什么 EXPLAIN 里出现 Using temporary 还带 Using filesort?
这说明 MySQL 不仅建了临时表,还在临时表上做了额外排序——通常是因为 GROUP BY 后又没加 ORDER BY NULL,而 MySQL 默认会对分组结果按分组字段再排一次序。
典型场景:
- 语句写成
SELECT uid, COUNT(*) FROM t GROUP BY uid;(隐式排序) - 优化手段:显式加上
ORDER BY NULL,如SELECT uid, COUNT(*) FROM t GROUP BY uid ORDER BY NULL;,可去掉Using filesort - 如果业务真需要排序,优先考虑让索引覆盖
GROUP BY + ORDER BY字段,避免二次排序
临时表结构长什么样?
MySQL 内部为 GROUP BY 构建的临时表,字段由分组列和聚合列组成,其中分组列通常是主键或唯一键(避免重复插入)。例如:
SELECT shop_id, SUM(amount) FROM orders GROUP BY shop_id; 对应的临时表结构近似:
CREATE TEMPORARY TABLE `group_temp` ( `shop_id` BIGINT PRIMARY KEY, `sum_amount` DECIMAL(18,2) DEFAULT 0 ) ENGINE=MEMORY;
每扫描一行,就按 shop_id 查主键:存在则累加 sum_amount,不存在则插入新行。这个过程在内存中完成,直到撑满 tmp_table_size 才刷到磁盘。
真正容易被忽略的是:即使你只查一个字段,只要没索引支撑、又没加 ORDER BY NULL,MySQL 就可能多走一遍排序——它不只建表,还悄悄给你排了次序。


















