group by字段未置于索引最左前缀必然触发using temporary;必须按分组顺序建联合索引(如group by a,b→索引为(a,b)),where等值条件可前置,但范围查询会截断索引排序能力,且需覆盖select字段、禁用隐式排序(加order by null)、确保统计信息最新(analyze table)。

GROUP BY字段没在索引最左前缀,临时表绕不开
看到Using temporary却只给分组字段建了INDEX(b, a),MySQL根本没法流式分组——a不在最左,优化器只能全表扫完再哈希归并。必须让分组字段从索引开头连续出现:GROUP BY user_id, status → 对应索引只能是INDEX(user_id, status),INDEX(status, user_id)无效。
- WHERE等值条件(如
status = 'paid')可前置,变成INDEX(status, user_id, status),但范围条件(>、BETWEEN)会截断后续字段的排序能力 - 如果查询含
HAVING COUNT(*) > 1,它发生在临时表之后,无法推动索引优化;想绕开,得把过滤逻辑提前到WHERE里 -
ANALYZE TABLE t必须执行,否则统计信息陈旧,优化器可能误判索引可用性
ORDER BY和GROUP BY字段顺序/方向不一致,索引白建
写成GROUP BY a, b ORDER BY b, a,哪怕建了INDEX(a, b)也触发Using temporary——索引顺序和查询逻辑冲突,MySQL无法复用物理有序性。MySQL 8.0+ 支持混合方向索引,但5.7及更早版本要求全部ASC或全部DESC才有效。
- 真实场景中,
GROUP BY user_id, status ORDER BY status, created_at→ 索引必须是INDEX(user_id, status, created_at),不能颠倒 -
ORDER BY NULL要显式加上,MySQL 5.7+ 默认对GROUP BY结果隐式排序,不加就大概率带Using filesort - 用
EXPLAIN FORMAT=TREE验证,输出里不含"using_temporary_table": true才算真正生效
SELECT字段未被覆盖,回表引发二次处理
即使索引结构对了,SELECT *或查了name但索引只有(user_id),MySQL仍要回主键聚簇索引捞数据——这本身不直接导致Using temporary,但一旦回表后字段顺序乱了,优化器可能放弃流式分组,退化为临时表兜底。
- 覆盖索引必须包含所有
SELECT非聚合字段,比如SELECT user_id, MAX(amount), name FROM t GROUP BY user_id→ 索引得是INDEX(user_id, name, amount) -
DISTINCT UPPER(name)这种写法必走临时表,函数包裹直接让索引失效,改用生成列+函数索引(MySQL 8.0+)或前置计算字段 - 字符集或校对规则不一致(如
utf8mb4_0900_as_csvsutf8mb4_general_ci)也会让索引无法用于排序,哪怕字段名和顺序都对
tmp_table_size和max_heap_table_size设不对,调参等于白干
这两个参数决定临时表能否留在内存里,但MySQL取二者中较小者作为上限。只改tmp_table_size不改max_heap_table_size,或者设成不同值,等于没设。
- 默认常为16MB,几万行聚合就容易撑爆;生产建议同步设为
67108864(64MB)至268435456(256MB) - 临时生效用
SET SESSION tmp_table_size = 67108864和SET SESSION max_heap_table_size = 67108864,但对已建立连接无效 - 必须监控
Created_tmp_disk_tables / Created_tmp_tables比值,持续高于5%说明仍频繁落盘,索引或SQL还有硬伤
ORDER BY NULL没加、ANALYZE TABLE没跑、以及把WHERE里的!=或IS NULL当成等值条件塞进联合索引最左——这些都会让前面所有索引设计失效。











