MySQL必须全表扫描是因为GROUP BY非索引列时无法利用索引有序性或哈希定位,只能逐行读取并在临时表中归类;EXPLAIN中出现Using temporary即确诊该问题,本质是“无索引可用”而非“不走索引”。

GROUP BY 非索引列时,MySQL 为什么必须扫全表?
因为分组操作依赖有序性或哈希定位,而数据库无法从非索引列中直接提取分组键的边界值或唯一值集合——它只能一行行读取所有数据,再在内存或磁盘临时表里归类。这本质上不是“不想走索引”,而是“根本没索引可走”。
EXPLAIN 中看到 Using temporary 就说明已 fallback 到全表扫描
只要执行计划里 Extra 字段出现 Using temporary(有时还带 Using filesort),基本可以断定:优化器放弃了索引路径,改用临时表聚合。这不是警告,是确诊结果。
-
type显示ALL或index(但key是 NULL)→ 没命中任何索引 -
rows接近表总行数 → 实际扫描量 ≈ 全表 - 即使
key非 NULL,但Extra仍有Using temporary→ 索引只用于 WHERE 或部分字段,GROUP BY 本身仍没利用上
为什么建了单列索引,GROUP BY a, b 还是不走?
复合分组必须匹配索引的最左前缀顺序。比如只建了 INDEX (a) 或 INDEX (b),对 GROUP BY a, b 完全无效;建了 INDEX (b, a) 也不行——顺序错,B+ 树无法按 a, b 的组合天然排序。
- 正确索引应为
INDEX (a, b)(严格顺序) - 若还有
WHERE c = ?,优先建INDEX (c, a, b),让过滤和分组共用一棵索引树 -
INDEX (a, b, c)可支撑GROUP BY a, b,但INDEX (a, c, b)不行——中间插了非分组列,破坏连续分组能力
最容易被忽略的“伪索引”陷阱
有些情况看似走了索引,实则 GROUP BY 仍全表扫描:索引字段被函数包裹、隐式类型转换、或 SELECT 中混入非分组非聚合字段,都会导致覆盖失效,触发回表或强制临时表。
-
GROUP BY DATE(created_at)→ 索引列created_at被函数包裹,索引失效 -
WHERE user_id = '123'(user_id是BIGINT)→ 字符串参数引发隐式转换,索引跳过 -
SELECT user_id, name, COUNT(*) FROM t GROUP BY user_id→name不在 GROUP BY 里,MySQL 5.7+ 默认报错;关掉ONLY_FULL_GROUP_BY后,引擎只能全表扫描选任意name值,无法利用索引
EXPLAIN 的 Extra 和索引定义的字段顺序,比加硬件更管用。

















