GROUP BY没走索引需先看EXPLAIN中type是否为index/range、key是否命中索引;常见原因包括分组字段无索引、函数操作导致失效、联合索引顺序错、TEXT字段分组或统计信息过期等。
GROUP BY没走索引?先看执行计划里的type和key
navicat里点「解释」(explain)后,重点盯住type列是否是index或range,而不是all;同时确认key列显示的是你为group by字段建的索引名。如果key是null,说明索引根本没被选中。
常见原因包括:
-
GROUP BY字段上没建索引,或建了但类型不匹配(比如字段是VARCHAR(255),索引只建在前10个字符上) - 查询里对
GROUP BY字段用了函数,例如GROUP BY UPPER(name),索引失效 - 表用了
TEXT或CLOB类型字段做分组,MySQL无法高效索引这类大字段 - 联合索引顺序不对:比如建了
INDEX (a, b),但写的是GROUP BY b,那这个索引基本用不上
ORDER BY + GROUP BY混用时,Extra里出现Using filesort怎么办
当语句类似SELECT a, COUNT(*) FROM t GROUP BY a ORDER BY a DESC,如果GROUP BY字段本身有索引,且ORDER BY方向与索引方向一致(比如索引是ASC,你也写ASC),MySQL可复用索引完成排序,Extra就不会出现Using filesort。
但如果写成ORDER BY a DESC而索引是默认ASC,InnoDB 8.0+ 虽支持反向扫描,但老版本或某些场景下仍会触发文件排序——这时要么显式建DESC索引(CREATE INDEX idx_a_desc ON t(a DESC)),要么干脆去掉ORDER BY(除非业务真需要)。
- 不要依赖
GROUP BY隐式排序,MySQL 8.0 后已明确不保证顺序 - 如果
ORDER BY字段和GROUP BY字段不同,几乎必然触发Using filesort,得评估是否真要这个排序 -
GROUP BY后加LIMIT有时能显著减少临时表开销,但注意它是在分组完才截断,不是提前剪枝
为什么rows值巨大,但实际结果只有几行?
rows是优化器预估的扫描行数,InnoDB下它基于统计信息估算,并非精确值。如果看到rows几十万、但GROUP BY后只剩几条,说明分组前数据过滤太弱——问题不在分组本身,而在前面的WHERE条件没生效。
典型表现:
-
WHERE条件字段没索引,导致全表扫描后再分组 - 用了
OR、!=、NOT IN等难走索引的操作 - 连接多张大表后才
GROUP BY,应考虑把过滤和分组尽量推到驱动表上 - 统计信息过期:
ANALYZE TABLE t能强制刷新,尤其在大批量导入/删除后
GROUP BY报错ERROR 1055,别急着关ONLY_FULL_GROUP_BY
这个错误不是性能问题,但会影响你能否顺利跑出执行计划。它意味着SELECT列表里有非聚合字段,且没出现在GROUP BY中,比如SELECT user_id, email FROM log GROUP BY user_id。
强行关掉模式(SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY','')))会让查询跑通,但返回的email值是任意一条记录的,业务逻辑可能出错。
- 正确做法是:补全
GROUP BY,或改用聚合函数包裹,如MAX(email) - Django/SQLAlchemy等ORM生成的
.values('user_id')类查询,要检查它是否自动带了多余字段 - Navicat「查询创建工具」里拖字段容易悄悄带出非分组字段,建议手写SQL并严格对照
GROUP BY列表
rows高、key为空、Extra带Using temporary这三者同时出现时,基本可以确定要重构查询逻辑或补索引了。











