MySQL执行GROUP BY时直接构建内存哈希表存储分组键与聚合状态,若分组键过多或单组数据过大导致内存溢出,便会写磁盘临时表;EXPLAIN中出现Using temporary即表明已触发该机制,需检查索引匹配性、函数使用、字段大小及tmp_table_size与max_heap_table_size配置一致性。

MySQL 执行 GROUP BY 时根本不会“先分组再挑数据”,而是把**每个分组键的完整值 + 对应聚合中间状态**(如 COUNT 计数器、SUM 累加值、COUNT(DISTINCT) 的哈希集)一起塞进内存哈希表。一旦分组键数量超千万,或某一分组(比如一个 user_id 对应 300 万条日志)的数据量过大,哈希表就直接撑爆内存——这时 MySQL 别无选择,只能把整张临时表写到磁盘。
Using temporary 就是溢出倒计时
这个提示不是警告,是确诊书。它说明优化器已放弃用索引顺序分组(Using index for group-by),转而构建全量内存哈希表。只要 EXPLAIN 的 Extra 列出现它,你就该停手检查:
- 是否
GROUP BY字段没索引,或索引顺序不匹配(比如索引是(status, created_at),但写成GROUP BY created_at, status) - 是否用了函数,如
GROUP BY DATE(created_at)或GROUP BY UPPER(name),导致索引失效 - 是否
SELECT *拖着大字段(TEXT、JSON、长VARCHAR)一起进临时表,单行体积翻几倍
tmp_table_size 和 max_heap_table_size 必须相等
这两个参数不是“建议设一样”,而是 MySQL **取其中较小值作为实际阈值**。常见错误配置:tmp_table_size = 512M,但 max_heap_table_size 仍为默认 16M —— 结果还是按 16M 跑,一碰百万级分组就落盘。
- 查当前值:
SELECT @@tmp_table_size, @@max_heap_table_size; - 永久生效:在
my.cnf的[mysqld]段里显式写两行,重启后用SHOW VARIABLES LIKE 'tmp_table_size';确认数值完全一致 - 别设太高:超过物理内存 20% 且并发高时,可能触发系统级 OOM,不是救查询,是埋雷
UUID 或大文本字段分组会加速崩溃
UUID 不是“慢”,是设计反模式:BINARY(16) 单键占 16 字节,100 万不同值仅键就吃掉 ~15MB,加上哈希头和聚合值,轻松突破默认 16M 限制。同理,GROUP BY long_url 或 GROUP BY json_col 会让单键膨胀至 KB 级,千万分组即数十 GB 内存需求。
- 别指望
tmp_table_size能兜底——调到 1G 也扛不住字段本身太大 - 真要分组,改用确定性哈希:
GROUP BY SHA2(long_text, 256)(MySQL 8.0+ 支持函数索引) - 绝对避免
GROUP BY LEFT(long_text, 100)这类操作:既不减内存,又让索引失效
最常被忽略的一点:即使你把所有参数调到顶、建了完美索引,只要查询里还带着 SELECT * 或 WHERE 条件无法下推(比如写成 HAVING COUNT(*) > 100 而非 WHERE status = 'active'),哈希表体积照样会指数级膨胀。优化永远从查询结构本身开始,而不是等它崩了再调参。


















