group by变慢本质是无索引或索引未覆盖导致全表扫描、临时表和文件排序;判断覆盖需explain中key显示索引且extra无using temporary/filesort,select字段全在索引列中。

为什么GROUP BY会突然变慢
本质是数据库在分组时需要临时排序或哈希,如果 GROUP BY 字段没索引,或者索引无法覆盖查询字段,就会触发全表扫描 + 临时表 + 文件排序。常见现象是执行计划里出现 Using temporary; Using filesort。
不是所有 GROUP BY 都慢——只有当分组字段无索引、或 SELECT 的字段超出索引列范围时,性能才会断崖式下跌。
怎么判断当前SQL是否能走覆盖索引
用 EXPLAIN 看 key 和 Extra 列:如果 key 显示用了某个索引,且 Extra 里没有 Using filesort 或 Using temporary,同时 SELECT 的所有字段(含 GROUP BY 列)都在该索引的定义列中,才算真正“覆盖”。
-
GROUP BY a, b时,索引必须是(a, b)或(a, b, c)这类左前缀结构,(b, a)不行 -
SELECT a, b, SUM(c)要走覆盖,索引至少得是(a, b, c);如果只建了(a, b),c还得回表查,不覆盖 - 注意隐式类型转换:比如
user_id是BIGINT,但WHERE user_id = '123'传了字符串,索引就失效,覆盖自然也崩了
建覆盖索引时最容易踩的三个坑
覆盖索引不是字段堆得越多越好,顺序、冗余和维护成本都得算进去。
- 把
GROUP BY字段放最左边,聚合字段(如SUM()、COUNT()依赖的列)放右边,例如GROUP BY status, category+SELECT status, category, COUNT(*)→ 索引建为(status, category)就够,不用加其他列 - 别在已有复合索引上重复建覆盖索引,比如已有
(a, b, c),又建(a, b),后者基本无效,还拖慢写入 -
TEXT/BLOB类型不能建索引,如果GROUP BY或SELECT里有这类字段,覆盖索引直接不可行,得考虑改字段类型或预计算字段
MySQL 5.7 vs 8.0 在覆盖索引上的关键差异
8.0 对函数索引和降序索引的支持,让某些场景下覆盖更灵活,但老版本用户容易误判。
- MySQL 5.7 不支持函数索引,所以
GROUP BY DATE(created_at)想覆盖,只能建生成列 + 索引;8.0 可直接建INDEX (DATE(created_at)) - 8.0 支持降序索引,如果
ORDER BY ... DESC和GROUP BY同时存在,且想避免Using filesort,可以建(a DESC, b ASC)这种混合顺序索引,5.7 只能全 ASC,排序逻辑可能不匹配 - 8.0 的
EXPLAIN FORMAT=TREE能更直观看到是否用了覆盖,5.7 只能靠Extra字段硬猜
覆盖索引不是银弹,尤其当分组维度极多(比如按小时+地区+设备类型三级分组)、数据量极大时,索引本身会变重,写入延迟和存储开销会上升。这时候得权衡:是加索引扛读,还是用物化视图/预聚合表减压。










