group by有索引但order by用非索引列会导致filesort;索引需覆盖select、where、group by、order by所有字段;where条件写法不当(如左模糊、范围前置)会破坏索引有序扫描能力。

GROUP BY字段有索引,但ORDER BY用了非索引列
这是最常见也最容易被忽略的原因。比如 SELECT dept, COUNT(*) FROM user GROUP BY dept ORDER BY name,即使 dept 上有索引,name 不在索引里,MySQL 就必须对分组结果再排序——Using filesort 会稳稳出现在 EXTRA 列里。
关键点在于:索引只能保证 GROUP BY 字段的物理顺序,不负责 ORDER BY 的输出顺序,除非后者字段也在同一索引中且顺序兼容。
- 若查询是
GROUP BY dept ORDER BY dept DESC,建INDEX(dept DESC)(MySQL 8.0+)可避免排序 - 若
ORDER BY COUNT(*) DESC,别挣扎了——聚合计算值不可能被索引覆盖,filesort必然触发 - 联合索引顺序必须匹配访问路径:
WHERE status = 1 GROUP BY dept ORDER BY created_at→ 索引应为(status, dept, created_at),不是(dept, created_at)
索引存在但未被真正“覆盖”
所谓“覆盖索引”,是指查询涉及的所有字段(SELECT 列 + GROUP BY 列 + WHERE 条件列 + ORDER BY 列)全部包含在同一个索引中。漏掉任意一个,就可能回表或触发排序。
例如 SELECT dept, MAX(salary) FROM user WHERE status = 1 GROUP BY dept,如果索引只有 (status, dept),那 MAX(salary) 就得回表取值,而回表后数据顺序已丢失,优化器大概率放弃索引排序逻辑,改走临时表 + filesort。
- 补全索引为
(status, dept, salary)才算真正覆盖,MAX(salary)可直接从索引叶子节点获取 -
SELECT *几乎永远无法被覆盖,别指望靠索引消除排序 - 注意 NULL 值处理:如果
salary允许 NULL,而索引未声明NOT NULL,某些版本下仍可能影响覆盖判定
WHERE 条件破坏了索引的连续扫描能力
即使索引建对了,WHERE 子句写法不对,也会让优化器无法利用索引的有序性来自然分组。本质是:GROUP BY 要快,得靠“索引扫描时数据天然按分组键排好序”,一旦 WHERE 截断了这个连续性,就退化成全量扫描 + 临时表。
-
WHERE dept LIKE '%tech':左模糊,索引失效,无法按dept有序扫描 → 必然Using temporary; Using filesort -
WHERE create_time > '2025-01-01' AND dept = 'dev',若索引是(create_time, dept),范围条件在前,dept就无法用于分组排序;应改为(dept, create_time) -
WHERE JSON_CONTAINS(tags, '"java"'):函数包裹字段,除非建函数索引(MySQL 8.0.13+),否则索引完全失效
执行计划里 type= index 但 Extra 还有 Using filesort
看到 type: index 就以为索引生效了?不一定。这说明 MySQL 在用索引全扫描(即遍历整个索引树),但它没按你期望的方式用——比如本该用索引做分组,结果只是拿它当数据源,再扔给临时表处理。
这时候要盯紧 key 和 Extra:key 显示用了哪个索引,Extra 出现 Using filesort 或 Using temporary 就是明确信号:索引没被用于分组/排序逻辑。
- 检查
EXPLAIN FORMAT=JSON中的group_key和ordering_operation字段,确认分组和排序是否真的下推到了存储层 - 统计信息过期会导致优化器误判,运行
ANALYZE TABLE user后再看执行计划 - MySQL 5.7 默认 SQL mode 严格时,
SELECT dept, name FROM user GROUP BY dept直接报错;宽松模式下虽能跑,但name值不可控,且强制启用临时表











