GROUP BY 本身不必然导致全表扫描,真正触发它的是分组字段无索引、WHERE条件未利用索引或SELECT字段超出索引覆盖范围;需按WHERE列、GROUP BY列、SELECT非聚合列顺序建立联合覆盖索引,并避免函数操作与隐式类型转换。

GROUP BY 本身不必然导致全表扫描,真正触发它的,是分组字段没索引、WHERE 条件没利用上索引,或 SELECT 字段超出索引覆盖范围。
为什么 GROUP BY 会触发全表扫描
数据库执行 GROUP BY 时,需要按分组字段排序或哈希。如果 GROUP BY 字段没有索引,或索引无法支持当前查询(比如顺序错、缺失 WHERE 列),优化器就只能扫全表构建临时结果集——这时 EXPLAIN 里会出现 Using temporary; Using filesort。
- 常见诱因:只建了
(b, a)索引,却写GROUP BY a, b - WHERE 中对索引列用了函数,比如
WHERE DATE(create_time) = '2024-01-01',索引失效 -
SELECT *或包含非索引字段,迫使回表,间接让索引“不够用”
怎么建索引才能让 GROUP BY 走索引
核心是让索引同时覆盖 WHERE 过滤 + GROUP BY 分组 + SELECT 输出字段,即“联合覆盖索引”。
- 顺序必须严格左前缀:
GROUP BY a, b→ 索引至少为(a, b);若还有WHERE c = ?,优先建(c, a, b) - 如果
SELECT包含sum(price),而 price 不在索引里,就得回表——此时应把 price 加到索引末尾,变成(c, a, b, price) - 避免冗余:
(a, b, c)已存在,再建(a, b)没意义,还拖慢写入
哪些写法会让索引彻底失效
即使索引建对了,SQL 写法不对照样白搭。这些操作会让优化器放弃使用索引:
- 在
GROUP BY字段上用函数:GROUP BY YEAR(create_time)→ 改成先用WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31'缩小范围,再GROUP BY原始字段 -
OR连接条件:WHERE status = 1 OR type = 'vip'→ 拆成UNION ALL或改用IN(前提是字段有索引) - 隐式类型转换:
WHERE user_id = '12345'(user_id 是BIGINT)→ 字符串强制转数字,索引失效;必须传整型参数
EXPLAIN 怎么看才算真正避开了全表扫描
不能只看 key 是否非 NULL,关键要三者同时满足:
-
key显示用了哪个索引(不是NULL) -
Extra里**没有**Using temporary和Using filesort -
SELECT所有字段(含GROUP BY列)都在该索引定义中——哪怕只多一个text字段没包含,也会回表,失去覆盖意义
最容易被忽略的是:索引列顺序错了、WHERE 条件漏了高选择性字段、或者以为建了单列索引就能撑起复合分组——实际几乎没用。

















