GROUP BY字段顺序必须与索引最左连续前缀逐字匹配才能利用索引有序性避免排序和临时表;否则必触发Using temporary和Using filesort,如GROUP BY status,user_id无法使用索引(user_id,status)。

GROUP BY字段顺序必须和索引最左前缀完全一致
MySQL 不会“用索引执行分组”,而是依赖索引的物理有序性来跳过排序和临时表。只有当 GROUP BY 列的出现顺序与联合索引的最左连续前缀**逐字匹配**时,才能触发松散索引扫描(Loose Index Scan),否则必然回退到 Using temporary 和 Using filesort。
常见错误现象:
-
GROUP BY status, user_id,却建了索引(user_id, status)—— 索引完全无效,因为status不在最左位置 -
GROUP BY a, c,索引是(a, b, c)—— 中间跳过b,最左链断裂,无法利用 -
GROUP BY user_id DESC, status ASC,但索引定义为(user_id, status)(默认都是 ASC)—— 方向不一致,5.7 及以前直接失效
WHERE + GROUP BY 组合决定索引字段顺序
索引不是为分组而建,是为整个访问路径服务:先过滤(WHERE),再分组(GROUP BY),最后可能排序(ORDER BY)。字段顺序必须按这个执行逻辑排布,否则后半段就废了。
例如查询:SELECT user_id, COUNT(*) FROM orders WHERE shop_id = 123 AND created_at >= '2024-06-01' GROUP BY user_id
- ✅ 推荐索引:
(shop_id, user_id, created_at)——shop_id等值过滤走最左,user_id紧随其后支持有序分组 - ❌ 错误写法:
(created_at, shop_id, user_id)——created_at是范围条件,导致后续字段无法高效利用 - ⚠️ 注意:
created_at放最后不是因为它不重要,而是因为范围查询会让它右边的索引字段全部失效
函数或表达式会让GROUP BY彻底失去索引能力
GROUP BY DATE(created_at) 看似合理,实则是索引杀手。MySQL 必须对每一行计算函数结果,无法复用 created_at 索引,哪怕你建了 (created_at) 也白搭。
正确做法是把时间逻辑下推到 WHERE,用范围比较替代函数分组:
SELECT DATE(created_at), COUNT(*) FROM orders WHERE created_at >= '2024-06-01' AND created_at < '2024-06-02' GROUP BY DATE(created_at);
- WHERE 先缩小数据集,GROUP BY 处理量级大幅下降
- MySQL 8.0+ 可建函数索引:
INDEX idx_date ON orders((DATE(create_time))),但老版本只能靠生成计算列 + 普通索引 - 隐式类型转换同理:
WHERE user_id = '123'(user_id是BIGINT)也会让索引失效
EXPLAIN里出现Using temporary就是索引没生效的明确信号
这不是警告,是已经失败的信号。一旦 EXPLAIN FORMAT=TRADITIONAL 输出中 Extra 列出现 Using temporary,基本可以确定当前索引没命中,或者查询本身在强迫数据库走低效路径。
- 检查方式很简单:
EXPLAIN SELECT ... GROUP BY ...,盯紧Extra列 - 别只看
key是否有值 —— 即使用了索引,若顺序错、字段缺、有函数,照样Using temporary - 覆盖索引不是“把 SELECT 所有字段都塞进索引”,而是确保
SELECT、WHERE、GROUP BY涉及的字段全在索引定义中,且顺序合规

















