mysql 5.7中group by常出现using temporary,因其默认隐式排序且无法利用索引顺序时需建临时表完成分组与排序;必须满足最左前缀、等值where、min/max紧邻等全部条件才能触发loose index scan。

GROUP BY 为什么总看到 Using temporary
因为 MySQL 5.7 默认会对 GROUP BY 结果隐式排序,而排序 + 分组逻辑若无法直接利用索引顺序,就必须建临时表来暂存、去重、聚合、再排序。只要执行计划里出现 Using temporary(有时还带 Using filesort),基本就说明它没走索引分组路径,而是退化成全表扫描 → 写临时表 → 排序 → 聚合的流程。
松散索引扫描(Loose Index Scan)的硬性条件
想让 MySQL 5.7 跳过临时表,必须满足全部以下条件,缺一不可:
-
GROUP BY列必须是单表上的联合索引最左前缀,比如索引是(user_id, created_at, status),那GROUP BY user_id或GROUP BY user_id, created_at可行;GROUP BY created_at或GROUP BY status就不行 - 查询中所有非
GROUP BY的列,必须是聚合函数且仅限MIN()、MAX(),且参数必须是紧接在GROUP BY后的索引列(例如GROUP BY a,则MIN(b)要求(a,b)是索引) - WHERE 条件中涉及的索引列,只能是等值条件(
=、IN、IS NULL),不能含范围条件(>、BETWEEN、LIKE 'abc%')——否则会退化为紧凑索引扫描(Tight Index Scan),仍可能用临时表 - 索引列不能是前缀索引,比如
INDEX(name(10))对GROUP BY name无效;必须是完整列索引
常见踩坑:明明建了索引,EXPLAIN 还是显示 Using temporary
不是索引存在就能用,关键看怎么用:
-
SELECT user_id, COUNT(*) FROM order GROUP BY user_id—— 如果只有INDEX(user_id),能用;但如果user_id是二级索引,而语句里又选了没被覆盖的列(比如SELECT user_id, order_no),就会回表,MySQL 5.7 往往放弃松散扫描改走临时表 -
GROUP BY和ORDER BY字段不一致,比如GROUP BY user_id ORDER BY created_at,即使两者都有索引,也会强制建临时表做二次排序 - 多表 JOIN 场景下,
GROUP BY列必须来自驱动表(即EXPLAIN中type为const或ref的第一张表),否则索引失效 -
HAVING中用了非索引字段或复杂表达式(如HAVING COUNT(*) > 10 AND AVG(price) > 100),会导致分组后还需过滤,临时表无法规避
真正有效的优化动作
别只盯着“加索引”,要对齐执行路径:
- 先
EXPLAIN看当前是否真走了索引;如果key为空或type是ALL,说明索引根本没被选中 - 把
SELECT列精简到最小集,优先只选GROUP BY列和COUNT(*);确认Extra字段出现Using index才算索引覆盖成功 - 避免在
GROUP BY查询里混用ORDER BY,除非字段完全一致;若必须排序,显式加ORDER BY并确保它复用同一索引 - WHERE 条件尽量用等值,少用范围;如果业务允许,可拆成多个等值查询(如
user_id IN (1,2,3))替代user_id > 100
松散索引扫描是个窄通道,不是“有索引就自动触发”。每一条条件都像卡口,卡住一个,就退回临时表老路。真正难的不是建索引,是让查询结构严丝合缝地贴合那个扫描逻辑。











