GROUP BY字段顺序必须与索引最左前缀严格一致才能避免Using temporary;如GROUP BY a, b仅能使用(a, b)或(a, b, c)索引,(b, a)或跳过前导列的索引均无效,否则触发临时表或filesort。

不能靠“加索引”消除 Using temporary,必须让索引字段顺序、覆盖范围和查询逻辑三者严丝合缝——否则建了也白建。
GROUP BY 字段顺序必须和索引最左前缀完全一致
MySQL 只能按索引定义的顺序“流式分组”,一旦顺序错位或跳过前导列,就立刻退化为临时表。比如:
- 查询写的是
GROUP BY a, b,索引却建成了INDEX(b, a)→ a 不在最左,无效 - 查询是
GROUP BY user_id, status,索引却是INDEX(status, user_id)→ 顺序颠倒,无法天然连续分组 - WHERE 中有
c = 1,又想GROUP BY a, b,最优索引是INDEX(c, a, b),不是INDEX(a, b, c)
SELECT 非聚合字段必须全部包含在索引中
只要 SELECT 列里出现没被 GROUP BY 覆盖的字段(比如 SELECT user_id, city, COUNT(*) 但只 GROUP BY user_id),MySQL 就必须用临时表协调语义——哪怕开了 ONLY_FULL_GROUP_BY 报错,关了也会返回不确定值,绕不开临时表。
- 正确做法:要么把
city加进GROUP BY,要么确保它在索引里紧接分组字段后(如INDEX(user_id, city))且查询明确列出字段(不用*) - 错误写法:
SELECT * FROM t GROUP BY a→ 强制触发临时表,无论索引多全 - 函数包裹直接失效:
GROUP BY UPPER(name)或DISTINCT YEAR(created_at)→ 必须用 MySQL 8.0+ 的函数索引才能救
ORDER BY 和 GROUP BY 混用时,索引必须一并覆盖且方向对齐
如果查询同时含 GROUP BY a ORDER BY a DESC,而索引是 INDEX(a)(默认 ASC),MySQL 8.0 以下版本仍会触发 Using filesort;即使不报错,也可能因排序路径误判连带启用临时表。
- MySQL 8.0+ 可建降序索引:
CREATE INDEX idx_user_age_desc ON t (user_id, viewed_user_age DESC) - 不需要结果排序?显式写
ORDER BY NULL,省掉优化器无谓判断 - 若
ORDER BY字段和GROUP BY完全无关(如GROUP BY a ORDER BY b),再好的索引也救不了——临时表是语义刚需,不是性能问题
别忘了 ANALYZE TABLE 和 EXPLAIN FORMAT=TREE
索引建对了,但统计信息陈旧,优化器照样选错执行路径;传统 EXPLAIN 显示 Using index,也不代表真没临时表——树形执行计划才能确认是否真的跳过了临时表构建步骤。
- 每次改完索引,先跑
ANALYZE TABLE t更新统计信息 - 验证时用
EXPLAIN FORMAT=TREE SELECT ...,重点找输出里有没有"using_temporary_table": true - 监控指标比参数更重要:
SHOW GLOBAL STATUS LIKE 'Created_tmp%',确保Created_tmp_disk_tables / Created_tmp_tables < 0.05
真正难的不是建索引,而是把 WHERE 条件、GROUP BY 字段、SELECT 列、ORDER BY 字段这四者在物理存储层压成一条线性扫描路径——漏掉任一环,MySQL 就只能兜底建临时表。


















