GROUP BY 字段必须是索引的最左前缀
MySQL 的
GROUP BY要走索引加速,前提是它能利用 B+ 树的有序性做分组聚合。这要求
GROUP BY后的字段顺序必须严格匹配某个复合索引的最左前缀。比如表有索引
INDEX idx_status_created (status, created_at),那么
GROUP BY status可用,
GROUP BY status, created_at也可用;但
GROUP BY created_at或
GROUP BY user_id, status就无法使用该索引。
常见错误是建了索引却没生效:只在
WHERE条件里用了索引字段,但
GROUP BY字段不在索引中或顺序错位,导致 MySQL 回表后排序再分组,触发
Using filesort和
Using temporary—— 这两个提示一出现,基本就说明没走索引优化。 用
EXPLAIN检查
type是否为
range或
ref,且
Extra中不含
Using filesort或
Using temporary如果
SELECT中有非
GROUP BY字段(如
SELECT name, COUNT(*) FROM t GROUP BY status),MySQL 8.0+ 默认拒绝执行(
sql_mode=ONLY_FULL_GROUP_BY),需显式聚合或改写 覆盖索引能进一步提速:把
SELECT中所有需要的列都包含进索引,避免回表,例如
INDEX idx_status_cnt (status, id)配合
SELECT status, COUNT(id) FROM t GROUP BY status
避免在 GROUP BY 字段上用函数或表达式
对
GROUP BY字段做计算会直接让索引失效。比如
GROUP BY DATE(created_at)、
GROUP BY UPPER(name)或
GROUP BY CONCAT(first_name, ' ', last_name),MySQL 无法用原始索引完成分组,只能全表扫描后计算再分组。
解决办法是提前物化计算结果:
加生成列(Generated Column)并为其建索引:ALTER TABLE orders ADD COLUMN created_date DATE AS (DATE(created_at)) STORED;<br>CREATE INDEX idx_created_date ON orders(created_date);或在业务层/ETL 中预计算并存入普通字段,然后对这个字段建索引 时间范围分组(如按天/月)尽量用区间查询替代函数:
WHERE created_at >= '2024-01-01' AND created_at 至少能让 <code>WHERE先走索引过滤,减少分组数据量
ORDER BY NULL 显式禁用隐式排序
MySQL 默认会对
GROUP BY结果按分组字段升序排序,即使你不需要。这个隐式排序会增加开销,尤其当分组键无索引或数据量大时。如果业务明确不要顺序,加上
ORDER BY NULL能跳过这一步。
示例对比:
SELECT status, COUNT(*) FROM orders GROUP BY status;
vs
SELECT status, COUNT(*) FROM orders GROUP BY status ORDER BY NULL;
后者在
EXPLAIN中
Extra字段不会出现
Using filesort—— 即使
status有索引,前者仍可能触发一次额外排序。 仅当确认下游不依赖返回顺序时才加
ORDER BY NULL如果同时需要
ORDER BY其他字段(如
ORDER BY COUNT(*) DESC),那就无法避免排序,此时应优先确保
GROUP BY索引有效,再考虑是否加
SQL_BIG_RESULT提示让优化器倾向使用磁盘临时表而非内存表
小结果集用松散索引扫描,大数据量慎用 DISTINCT + GROUP BY
当
GROUP BY字段区分度高、分组数少,且索引覆盖完整时,MySQL 可能启用“松散索引扫描(Loose Index Scan)”,即跳着读索引节点,大幅减少访问行数。这是最理想的执行路径,
EXPLAIN中
type会显示为
index,且
rows很小。
但要注意两个典型陷阱:
DISTINCT和
GROUP BY在语义等价场景下混用(如
SELECT DISTINCT status FROM tvs
SELECT status FROM t GROUP BY status),MySQL 有时会选更差的执行计划,建议统一用
GROUP BY并确保索引存在 聚合函数含非分组字段时(如
MAX(updated_at)),若该字段不在索引中,即使
GROUP BY字段走了索引,仍需回表取值——此时应把被聚合字段也加入索引,形成覆盖,例如
INDEX idx_status_updated (status, updated_at)
索引不是越多越好,但针对
GROUP BY的索引必须紧扣实际查询结构:字段顺序、是否覆盖、是否避免计算 —— 少一个条件,优化就可能落空。
