EXPLAIN中出现Using temporary或Using filesort即表明GROUP BY未走覆盖索引,核心判断依据是type非ALL/index、key显示有效索引、Extra无二者;索引顺序必须严格为WHERE等值字段→GROUP BY字段→SELECT非聚合字段,且禁用函数与隐式转换。

EXPLAIN里出现Using temporary或Using filesort就说明没走覆盖索引
只要执行计划的Extra列里出现Using temporary或Using filesort,基本可以断定当前GROUP BY没用上覆盖索引。这不是“可能慢”,而是MySQL已明确启用临时表+内存/磁盘排序路径——性能瓶颈就在这里。
判断依据只有三个关键字段:type不能是ALL或index(应为ref/range),key必须显示实际使用的索引名,Extra里不能含那两个关键词。三者缺一不可。
-
rows显示扫描行数接近总行数?说明索引没生效,或者WHERE条件根本没走索引 - 即使
GROUP BY字段本身有单列索引,但前面WHERE用了其他字段,仍会全表扫描 - MySQL 8.0 可用
EXPLAIN FORMAT=TREE直接看到索引访问路径;5.7只能靠Extra硬推
索引字段顺序错了,再宽的索引也白搭
覆盖索引不是把所有用到的字段堆进去就行,顺序决定它能不能被GROUP BY复用。核心规则只有一条:WHERE条件字段最左,GROUP BY字段紧随其后,SELECT中非聚合字段放末尾。
- 查询
SELECT user_id, MAX(created_at) FROM orders WHERE status = 'paid' GROUP BY user_id,理想索引是(status, user_id, created_at) - 建
(user_id, status)或单独(user_id)基本无效——MySQL无法先按user_id排序再去过滤status -
GROUP BY a, b时,索引必须是(a, b)或(a, b, c)这类左前缀结构;(b, a)不行,(a)也不行 - 如果还带
ORDER BY COUNT(*) DESC,索引再好也救不了——计算值不可能被索引覆盖,必然Using filesort
函数、隐式转换、NULL值会让索引彻底失效
一旦在GROUP BY或WHERE中对字段做函数处理、表达式运算或类型转换,索引的连续访问路径就被打断,强制退化为全表扫描。
-
GROUP BY DATE(create_time)→ 时间索引失效;改用WHERE create_time >= '2024-01-01' AND create_time 配合生成列 -
WHERE user_id = '123'(user_id是BIGINT)→ 隐式转换导致索引失效;显式写成WHERE user_id = CAST('123' AS UNSIGNED) -
WHERE col IS NULL可能让优化器放弃索引;有时加WHERE col IS NOT NULL反而能激活索引 - TEXT/BLOB字段不能建索引,如果
GROUP BY或SELECT里含这类字段,覆盖索引直接不可行
覆盖索引不是字段越多越好,冗余和维护成本得算进去
把remark、content这类大字段塞进索引,写入延迟和缓存压力会陡增。真正需要的是最小可行覆盖:GROUP BY字段 + WHERE字段 + 聚合依赖字段。
- 查询
SELECT status, category, COUNT(*) FROM logs GROUP BY status, category,索引建(status, category)就够了,不用加其他列 - 已有
(a, b, c)索引,再建(a, b)基本无效,还拖慢写入 - MySQL 5.7不支持函数索引,
GROUP BY DATE(created_at)想覆盖,只能建生成列+索引;8.0可直接建INDEX (DATE(created_at)) - 分组维度极多(比如按小时+地区+设备类型三级分组)、数据量极大时,索引本身会变重,存储开销和写入延迟会上升

















