group by 没走索引的本质是未能利用索引的有序性,常见原因包括索引列顺序不匹配、使用函数或非索引列、select 中含非分组非聚合字段、where 条件未走索引、order by 与 group by 冲突等,需通过 explain 验证并优化索引设计与查询写法。

GROUP BY 没走索引,本质是没用上索引排序能力
MySQL 的 GROUP BY 要高效,前提是它能复用索引的有序性。如果索引列顺序和 GROUP BY 列不一致,或中间夹了非索引列、函数、表达式,优化器就无法利用索引完成分组,只能先全表扫描再内存排序聚合——这就是你看到的“慢”根源。
常见错误现象:EXPLAIN 显示 type=ALL 或 Extra 里出现 Using temporary; Using filesort;执行时 CPU 和临时表 I/O 飙升。
- 确保
GROUP BY列全部来自同一个复合索引,且顺序严格匹配索引定义(例如索引是(user_id, status, created_at),那GROUP BY user_id, status可以走索引,但GROUP BY status, user_id就不行) - 避免在
GROUP BY列上套函数,比如GROUP BY DATE(created_at)—— 改成先按日期范围过滤,再对原始列分组 - 不要混用索引列和非索引列:
GROUP BY user_id, remark(remark无索引)会导致整个索引失效
SELECT 中的非分组字段引发回表或临时表
当你写 SELECT user_id, MAX(amount), name FROM orders GROUP BY user_id,而 name 不在 GROUP BY 里,MySQL 5.7+ 默认拒绝(sql_mode 含 ONLY_FULL_GROUP_BY),但即使关掉,name 的值也无法确定来源行——引擎只能全表扫描+临时表聚合,或强制回表取任意一行,破坏索引优势。
- 只选真正需要的、属于分组键或聚合函数内的字段,例如
SELECT user_id, COUNT(*), AVG(amount) - 若必须带额外字段(如最新一条订单的
order_no),改用ROW_NUMBER()窗口函数 + 子查询,或关联MAX(id)后再 JOIN,避免在GROUP BY里硬塞非确定字段 - 检查是否启用了覆盖索引:把所有 SELECT 字段都包含进复合索引,例如
INDEX (user_id, amount, order_no),可避免回表
小结果集也慢?可能是聚合前没过滤
很多人以为“数据量不大就没问题”,但 GROUP BY 是在 WHERE 过滤之后才执行的。如果 WHERE 条件没走索引,照样先全表扫描,再分组——哪怕最终只返回 10 行。
- 优先保证 WHERE 条件能命中索引,特别是时间范围、状态码等高频筛选字段;例如
WHERE created_at >= '2026-06-01'比WHERE YEAR(created_at) = 2026更友好 - 避免
OR、IN大列表、LIKE '%keyword'这类必然触发全表扫描的条件出现在聚合前 - 对超大表,考虑加
LIMIT前置分页(如先SELECT user_id FROM orders WHERE ... LIMIT 1000,再基于这 1000 个 ID 做精确聚合)
ORDER BY + GROUP BY 组合最容易踩坑
当语句里同时出现 GROUP BY 和 ORDER BY,且两者字段不完全重叠或顺序不一致,MySQL 很可能放弃索引,转而用临时表排序——这是隐性全表扫描高发区。
- 如果
ORDER BY字段是聚合结果(如ORDER BY COUNT(*) DESC),索引完全无效,只能靠内存排序;此时应评估是否真需实时排序,或改用物化汇总表 - 若
ORDER BY和GROUP BY字段一致(如都用user_id),且索引顺序匹配,才可能复用索引避免二次排序 - 显式加
ORDER BY NULL可禁用默认排序,减少开销,但仅适用于业务明确不需要排序的场景
GROUP BY 或 SELECT 里出现一个非索引字段、一个函数、一个错序,整条链路就退回全表扫描。验证永远靠 EXPLAIN,而不是靠“应该可以”。











