group by没走索引的根本原因是优化器判定走索引代价高于全表扫描,典型表现为explain出现type:all或extra:using temporary; using filesort;需确保where条件命中索引、group by字段顺序与联合索引严格一致、select字段全部被覆盖,三者缺一即触发全表扫描。

GROUP BY没走索引,优化器只能扫全表
GROUP BY本身不触发全表扫描,真正让它退化的是优化器发现:走索引比直接扫表代价更高。典型信号是 EXPLAIN 里出现 type: ALL 或 Extra: Using temporary; Using filesort。这时候数据库已经放弃索引,改用内存哈希或磁盘临时表做分组——数据量一上去,CPU 和 I/O 就飙升。
WHERE条件没命中索引,GROUP BY前就已全扫
很多人误以为“WHERE筛得少,GROUP BY就快”,但执行顺序是 WHERE → GROUP BY → HAVING。如果 WHERE 里的字段没索引,或者写了 WHERE YEAR(create_time) = 2025 这类函数操作,优化器根本没法提前过滤,只能先全表读出所有行,再分组。
-
WHERE status = 'active' AND create_time >= '2025-01-01'能走(status, create_time)索引;但WHERE create_time >= '2025-01-01' AND status = 'active'在旧版本 MySQL 中可能只用上create_time单列部分 - 用
OR连接不同字段(如WHERE a = 1 OR b = 2)基本等于放弃索引,考虑拆成UNION ALL -
LIKE '%abc'、隐式类型转换(如user_id = '123'但字段是BIGINT)同样让 WHERE 阶段失效
GROUP BY字段顺序和索引不匹配
联合索引 (a, b, c) 只能高效支持 GROUP BY a、GROUP BY a, b、GROUP BY a, b, c。一旦写成 GROUP BY b, a 或 GROUP BY b,索引的有序性就断了,优化器无法复用B+树结构做松散扫描(Loose Index Scan),只能回退到全表扫描+排序。
- MySQL 8.0 前不支持混合 ASC/DESC 排序,
GROUP BY a ASC, b DESC即使有(a, b)索引也大概率失效 -
GROUP BY列必须全部来自同一个索引,且不能夹杂非索引列(如GROUP BY user_id, remark,而remark无索引) -
key_len在EXPLAIN中远小于预期值,说明索引只用了一部分,分组阶段实际没生效
SELECT字段超出索引覆盖范围
即使 GROUP BY 走了索引,只要 SELECT 里有非分组、非聚合、且不在索引中的字段,就会触发回表——而回表开销大到一定程度,优化器宁愿全表扫描一次搞定。
- 写
SELECT user_id, COUNT(*), name FROM orders GROUP BY user_id,若name不在索引里,就不是“覆盖” - 正确做法是建联合索引
(user_id, name),或彻底去掉name改用子查询关联最新记录 -
TEXT/BLOB字段无法被索引包含,一旦出现在SELECT或GROUP BY中,覆盖索引直接不可行











