group by慢八成因索引未对齐,关键看explain的extra列:出现using temporary或using filesort即分组未走索引;需按where→group by→select顺序建联合索引,且字段顺序必须严格匹配,避免函数、类型转换等导致索引失效。

GROUP BY慢,八成不是数据量问题,而是索引没对上——哪怕EXPLAIN显示用了索引,只要Extra里有Using temporary或Using filesort,就说明分组根本没走索引加速。
怎么看GROUP BY是否真走了索引
别只盯key列是否非NULL;重点看Extra列:
- 出现
Using temporary→ MySQL被迫把所有匹配行读进临时表再分组,哪怕只有80万行,也可能从200ms跳到8秒+ - 出现
Using filesort→ 分组后还要排序,且索引无法天然提供有序输出 -
type是ALL或index(而非ref/range)→ 索引没被有效过滤,大概率失效 -
key_len异常大(比如VARCHAR(255)字段只查前10字符,但key_len显示765)→ 索引定义和查询条件不匹配,前缀索引没生效
建什么索引才真正管用
联合索引顺序必须严格按 WHERE → GROUP BY → SELECT 字段排列,否则B+树无法支持松散索引扫描(Loose Index Scan):
- 错误示例:
SELECT user_id, COUNT(*) FROM orders WHERE status = 1 GROUP BY user_id,却建了INDEX(user_id, status)→WHERE条件无法利用最左前缀,索引废掉 - 正确写法:先
status(高区分度),再created_at(范围条件),再user_id(分组字段)→INDEX(status, created_at, user_id) - 若还要
SELECT MAX(amount),把amount加到末尾形成覆盖索引:INDEX(status, created_at, user_id, amount),避免回表 - 别在
GROUP BY里用函数:GROUP BY DATE(created_at)会让索引完全失效;改用冗余字段created_date DATE+ 索引,或先用范围缩小数据量
为什么加了索引还是慢?常见隐形坑
索引存在 ≠ 索引生效,这几个细节最容易被忽略:
- 隐式类型转换:如
user_id是BIGINT,但写WHERE user_id = '123'(字符串)→ 索引失效,覆盖索引也崩 - 覆盖不全:
GROUP BY a, b,但索引只建了(a, b),而SELECT还要c→ 必须回表,Extra仍可能出Using temporary - MySQL 5.7不支持函数索引:
INDEX(DATE(created_at))无效,别白费力气 - ORDER BY干扰:即使
GROUP BY字段有索引,但ORDER BY SUM(x) DESC这种聚合后排序,仍会触发Using filesort,得靠覆盖+预排序逻辑绕开
DISTINCT有时比GROUP BY快得多
当语义等价(仅去重无需聚合)时,DISTINCT可能跳过分组哈希/排序阶段,直接利用索引唯一性快速返回:
- 例如
SELECT DISTINCT user_id FROM orders WHERE ...,如果user_id上有合适索引,可能比GROUP BY user_id快一个数量级 - 这不是玄学:MySQL对
DISTINCT的优化路径更激进,尤其在无聚合、无排序场景下 - 但注意前提:业务确实只需要去重,不需要
COUNT(*)、SUM()等聚合结果
真正卡住GROUP BY性能的,往往不是“要不要建索引”,而是“索引建在哪、怎么排、查的时候有没有悄悄把它废掉”。Using temporary不是警告,是确诊书——看到它,就该立刻检查WHERE和GROUP BY字段是否被同一个联合索引按序覆盖,而不是继续调buffer或加内存。










