不能完全消除但能彻底避开using temporary和using filesort;根源在于索引是否使group by字段物理连续,需按where等值列、group by列、select非聚合列顺序构建复合索引,并强制添加order by null。

不能完全消除,但能彻底避开 Using temporary 和 Using filesort —— 关键不在“排序开销”,而在“是否被迫建临时表”。
为什么 GROUP BY 会触发 Using temporary
MySQL 执行 GROUP BY 时,必须让相同分组值的行物理上连续(才能边扫边聚),这依赖索引的天然有序性。一旦索引无法提供这种顺序,它就只能:捞出所有符合条件的行 → 写进临时表 → 对临时表排序 → 再分组。这个“写临时表”就是 Using temporary 的根源。
常见误判是以为“数据少就没事”,其实只要索引列顺序不匹配 GROUP BY 字段顺序,哪怕只有 100 行,也会触发临时表。
-
GROUP BY a, b要求索引必须是(a, b)或(a, b, c),(b, a)无效 -
WHERE status = 1 GROUP BY category,索引应为(status, category),而非单独(category) - 如果
SELECT中有name,而索引只有(status, category),就会回表——回表本身不触发Using temporary,但可能让优化器放弃走该索引,间接导致临时表
ORDER BY NULL 是唯一能关掉隐式排序的开关
MySQL 5.7+ 已移除 GROUP BY 隐式排序语义,但优化器仍会“假设你要排序”,从而选错执行路径。不加 ORDER BY NULL,即使你没写 ORDER BY,执行计划里也大概率出现 Using filesort。
这不是可选项,是必须项:
- 原查询:
SELECT category, COUNT(*) FROM product WHERE status = 1 GROUP BY category; - 优化后:
SELECT category, COUNT(*) FROM product WHERE status = 1 GROUP BY category ORDER BY NULL; - 加了之后,
EXPLAIN的Extra列中Using filesort消失,且不会影响结果顺序(本来就不保证)
覆盖索引要同时满足三个字段位置约束
所谓“覆盖”,不是把所有字段堆进索引就行,而是必须按 MySQL 的访问逻辑排布:
-
最左是 WHERE 等值条件:如
status = 'active',放索引第一位 -
中间是 GROUP BY 字段(严格顺序):如
GROUP BY dept, role,索引必须是(status, dept, role) -
最右是 SELECT 中的非聚合字段:如
SELECT dept, role, MAX(salary),则索引需为(status, dept, role, salary);若只查COUNT(*),salary就不用加
注意:MAX(salary) 能走索引的前提是 salary 紧接在分组字段之后;如果索引是 (status, dept, role, name, salary),中间插了 name,Loose Index Scan 就不会启用,照样落临时表。
别信 sort_buffer_size,它对 GROUP BY 完全无效
调 sort_buffer_size 只影响显式 ORDER BY 排序阶段,和 GROUP BY 是否建临时表毫无关系。它控制的是每连接独占的内存块,设大了反而容易引发 OOM。
真正管临时表大小的是这两个参数,且必须同步改:
tmp_table_sizemax_heap_table_size
但它们只是防落盘的兜底手段,不是优化方向。看到 Using temporary,第一反应永远是检查索引结构,而不是调参。
最容易被忽略的一点:索引列顺序错误时,EXPLAIN 仍可能显示 key 用了某个索引,但 Extra 里赫然写着 Using temporary——这说明索引被用来过滤或扫描了,但没用于分组。此时别看 key,盯紧 Extra。











