有Using temporary说明MySQL正用临时表分组,性能已严重下降;需重点检查EXPLAIN的Extra列(Using temporary、Using filesort、Using index)、type和rows值,并确保复合索引顺序严格匹配WHERE等值条件→GROUP BY字段→SELECT非聚合字段。

直接看执行计划里有没有 Using temporary —— 有,说明 MySQL 正在用临时表做分组,性能基本已经掉坑里了。
查执行计划时重点盯哪几列
运行 EXPLAIN 后,别只扫一眼 type 和 key,真正要命的是 Extra 列:
-
Using temporary:必须优化,表示分组无法走索引排序,强制建哈希表或临时表 -
Using filesort:哪怕没ORDER BY,GROUP BY 也可能触发,说明分组后还要额外排序 -
Using index出现在Extra里才代表走了覆盖索引;只在key列显示索引名不等于有效利用 -
rows值远大于实际分组数(比如rows=1200000,但只有 200 个customer_id):说明扫描太多行才完成分组,索引没对上顺序
建复合索引的顺序不能错
GROUP BY 本质依赖有序性 —— 要么靠索引天然有序,要么自己排序。所以索引字段顺序必须严格匹配分组逻辑:
- 纯
GROUP BY a→ 索引至少包含(a),最好加聚合字段如(a, amount)形成覆盖 -
WHERE status = 'active' GROUP BY dept_id→ 索引必须是(status, dept_id),把过滤字段放前面 -
GROUP BY dept_id, team_id→ 索引必须是(dept_id, team_id),反过来(team_id, dept_id)就无效 - 如果还带
ORDER BY dept_id,那没问题;但要是ORDER BY team_id,这个索引依然无法避免Using filesort
WHERE 条件写错位置会让索引白建
很多人建了 (dept_id, status) 索引,但写成 WHERE status = 'X' GROUP BY dept_id —— 看似合理,实际可能不走索引。因为 MySQL 的最左前缀匹配是“连续段”,status 在第二位,无法单独跳过 dept_id 去用它。
- 正确写法:
WHERE dept_id IN (1,2,3) AND status = 'active' GROUP BY dept_id,能用(dept_id, status) - 错误写法:
WHERE status = 'active' GROUP BY dept_id,即使有(status, dept_id)索引,也只用于过滤,分组仍可能回表或建临时表 - 更隐蔽的坑:
WHERE dept_id > 0 AND status = 'active',范围条件(>)会截断后续索引字段的使用,status实际失效
高基数字段分组容易触发磁盘临时表
对 user_id(UUID)、email、ip_address 这类唯一值极多的字段做 GROUP BY,tmp_table_size 很快撑爆,MySQL 自动把内存临时表刷到磁盘,I/O 直接拉垮。
- 检查是否真需要按原始字段分组:能否先归类?比如
GROUP BY SUBSTRING(email, -3)改成GROUP BY domain_part(预计算列) - 确认
tmp_table_size和max_heap_table_size设置是否合理(通常建议设为 64M–256M),但治标不治本 - 若必须按高基数字段聚合,考虑改用
DISTINCT+ 应用层合并(实测某些场景比GROUP BY快一个数量级) - 千万级表上跑
GROUP BY uuid,基本等同于全表哈希,没有索引能救 —— 这时候该想是不是架构出了问题
真正卡住 GROUP BY 性能的,往往不是 SQL 写得不够“高级”,而是索引字段顺序和 WHERE 条件之间的微妙错位,以及对高基数分组后果的低估。执行计划里的 Using temporary 不是警告,是判决书。

















