根本解法是用sha2/md5哈希或substring_index等确定性截断替代原始大字段,避免select*和where索引失效;调大tmp_table_size仅延缓崩溃,不能解决哈希表内存膨胀本质问题。

聚合查询的临时表开销,主要来自内存不足被迫落盘、大字段哈希膨胀、以及SQL写法触发了本可避免的内部临时表——调大 tmp_table_size 只是止痛药,不是解法。
GROUP BY 大字段直接撑爆内存,别信“加内存就能好”
数据库执行 GROUP BY 时,会把每个分组键(比如 VARCHAR(2000) 或 JSON 字段)和聚合中间值全量缓存在内存哈希表里。单个键占几 KB,一千万不同值就轻松吃掉几十 GB 内存——根本没机会走到计算那步。
- 错误做法:
GROUP BY LEFT(long_text, 100)—— 不减内存占用,还让索引失效 - 错误做法:
GROUP BY JSON_EXTRACT(data, '$.body')—— 字段体积大,又无法走函数索引(除非显式建) - 真正可行:
GROUP BY SHA2(long_text, 256)或MD5(long_text),固定 32–64 字节,冲突可控 - 必须配套:
CREATE INDEX idx_hash ON t (SHA2(text_col, 256))(MySQL 8.0+)或CREATE INDEX idx_hash ON t ((md5(text_col)))(PostgreSQL)
EXPLAIN 出现 Using temporary 就该立刻干预
这不是提示,是明确信号:当前执行计划必须依赖临时表。它常和 Using filesort 同时出现,说明排序/分组都没走索引。
- 典型诱因:
GROUP BY DATE(created_at)—— 函数操作让created_at索引失效 - 修复方式:改用范围条件 + 覆盖索引,例如
WHERE created_at >= '2024-01-01' AND created_at ,并建索引 <code>(created_at, user_id, amount) - 类型隐式转换也会触发:
GROUP BY user_id(VARCHAR类型),但 JOIN 条件写了ON t1.user_id = 123(INT 字面量),导致索引失效 + 临时表 - 检查
sql_mode是否含ONLY_FULL_GROUP_BY,它不直接导致临时表,但能暴露非聚合字段滥用问题
SELECT * 和 WHERE 位置错位,会让临时表体积翻倍
你以为只是 GROUP BY 慢?其实 SELECT * 会把整行加载进临时表;HAVING 过滤比 WHERE 晚,意味着先分组再过滤,数据量早已爆炸。
- 永远只
SELECT真正需要的字段,尤其避开TEXT、JSON、BLOB -
WHERE条件必须放在GROUP BY之前,且尽量用索引列裸值比较(如WHERE status = 'paid',而非WHERE UPPER(status) = 'PAID') - 避免在聚合后排序原始大字段:
ORDER BY long_text—— 哈希后顺序已丢失,真要排序得额外关联原表 - 监控关键指标:
SHOW STATUS LIKE 'Created_tmp%',若Created_tmp_disk_tables / Created_tmp_tables > 5%,说明 SQL 或索引设计有问题
覆盖索引能绕过临时表,但得写对
如果索引能“覆盖”整个查询所需字段(WHERE + GROUP BY + SELECT 列),MySQL 就无需回表,也大概率不用临时表做中间缓存。
- 反例:
SELECT name, age FROM user WHERE city='Beijing' ORDER BY age,只有(city)索引 → 触发Using filesort和可能的Using temporary - 正解:建联合索引
(city, age, name)—— 支持过滤、排序、取值三合一 - 注意字段顺序:
ORDER BY age DESC, name ASC需对应索引(city, age DESC, name ASC)(MySQL 8.0+ 支持混合方向) - EXPLAIN 中
Extra列不再出现Using temporary或Using filesort,才算真正绕过
最常被忽略的一点:临时表是否落盘,取决于 tmp_table_size 和 max_heap_table_size 的较小值,而不是单看一个参数;而哪怕只超 1 字节,整个表就落地。所以结构优化永远比参数调优更可靠——尤其是当字段定义本身就很宽时。











