group by 查询慢主因是索引未覆盖select中的非聚合字段,正确做法是建组合索引(where字段、group by字段、非聚合字段)按序排列,如idx_status_user_created(status, user_id, created_at)。

直接结论:GROUP BY 查询慢,90% 以上是因为索引没覆盖 SELECT 中的非聚合字段,而不是 GROUP BY 本身慢。
为什么加了 GROUP BY 字段索引还是慢?
常见错误是只给 GROUP BY 字段建单列索引,比如 CREATE INDEX idx_user_id ON orders(user_id)。但查询里还有 SELECT user_id, COUNT(*), MAX(created_at) —— 这时 MySQL 查完 user_id 还得回聚簇索引捞 created_at,每组都回表,I/O 爆增。
- 索引必须包含所有
WHERE过滤字段(最左)、GROUP BY字段(中间)、SELECT中的非聚合字段(末尾) - 顺序错一个就失效:
(user_id, created_at)对WHERE status = 'paid' GROUP BY user_id几乎无用,因为status不在最左 -
MAX(created_at)、MIN(price)、AVG(score)这类字段,只要出现在SELECT里且不是GROUP BY列,就必须进索引末尾
怎么写组合索引才真正覆盖 GROUP BY 全链路?
以这个典型慢查询为例:SELECT user_id, COUNT(*), MAX(created_at) FROM orders WHERE status = 'paid' GROUP BY user_id
- 正确索引:
CREATE INDEX idx_status_user_created ON orders(status, user_id, created_at) - 解释:
status满足 WHERE 过滤;user_id支持分组(B+ 树天然有序);created_at覆盖MAX(),避免回表 - 如果后续加了
ORDER BY COUNT(*) DESC LIMIT 10,这个索引依然无效——因为 B+ 树无法按聚合结果排序,此时得靠物化视图或应用层缓存
哪些字段不该塞进索引?
盲目堆字段只会让索引变大、写入变慢、缓存命中率下降。
-
COUNT(*)不需要索引字段支持,它只统计行数,不取值 -
WHERE中用了函数就别指望索引:比如WHERE DATE(created_at) = '2026-04-24'会让created_at索引失效 -
TEXT或超长VARCHAR字段不建议加入索引,可用前缀索引替代,如name(10) - 如果
SELECT里有*或大量字段,说明查询设计有问题,应收敛到必要字段再建覆盖索引
真正难的不是建索引,而是判断哪些字段属于“非聚合但必须取值”——比如 MAX(updated_at) 是,COUNT(*) 就不是。漏掉前者,索引就白建;塞进后者,反而拖慢写入。











