GROUP BY慢不一定没走索引,但索引列顺序必须严格匹配GROUP BY列顺序且不能跳过前导列;函数、NULL值、列顺序错误均会导致索引失效。

GROUP BY慢,是不是没走索引?
不是所有 GROUP BY 都能用上索引。MySQL(含8.0+)、PostgreSQL 等主流引擎只在特定条件下复用索引:**索引列顺序必须严格匹配 GROUP BY 子句的列顺序,且不能跳过前导列**。比如 GROUP BY a, b 能用 (a, b, c) 索引,但用不了 (b, a) 或 (a, c)。
常见误判现象:EXPLAIN 显示 type=ALL 或 Extra 里有 Using temporary; Using filesort —— 这说明引擎被迫建临时表排序分组,索引完全失效。
多列索引该按什么顺序写?
核心原则:**把 GROUP BY 列放在最左,WHERE 条件列紧随其后(如果存在),ORDER BY 列可追加在末尾(仅当需要避免额外排序)**。顺序错一个,索引就废一半。
例如查询:SELECT dept, status, COUNT(*) FROM user WHERE city = 'Beijing' GROUP BY dept, status ORDER BY status
最优索引是:CREATE INDEX idx_dept_status_city ON user (dept, status, city) —— 注意 city 放最后,因为它是 WHERE 条件,不是分组依据;ORDER BY status 已被覆盖,无需额外排序。
-
(city, dept, status)❌:前导列city不在GROUP BY中,索引无法用于分组 -
(dept, city, status)⚠️:city插在中间,导致status无法被索引覆盖(非连续前缀),ORDER BY失效 -
(dept, status)✅:基础可用;但加上city可让WHERE也走索引,减少回表
NULL值和函数会让索引直接失效
只要 GROUP BY 表达式里出现函数或类型转换,比如 GROUP BY UPPER(name)、GROUP BY DATE(create_time),哪怕有对应字段索引,也必然退化为全表扫描。
同理,列允许 NULL 且实际含大量 NULL 值时,部分引擎(如旧版 MySQL)可能拒绝使用索引做分组,因为 NULL 不参与 B+ 树排序比较逻辑。
实操建议:
- 避免在
GROUP BY中使用函数;必要时建函数索引(MySQL 8.0+ 支持CREATE INDEX ... (UPPER(name))) - 确认分组列
NOT NULL;若业务允许,设为NOT NULL并补默认值 - 用
EXPLAIN FORMAT=TREE(MySQL 8.0)或EXPLAIN (ANALYZE, BUFFERS)(PostgreSQL)验证是否真走了索引扫描而非索引查找
聚合字段要不要放进索引?
不需要。索引本身不存储聚合结果,只加速分组过程。把 COUNT(*)、SUM(amount) 对应的字段塞进索引,只会增大索引体积、拖慢写入,对 GROUP BY 性能无帮助。
唯一例外:覆盖索引场景。比如查询 SELECT dept, COUNT(*) FROM user GROUP BY dept,而你建了 (dept) 索引——此时引擎可仅扫描索引树完成分组,无需回表查数据页。但如果还查 name 或 email,那就得回表,索引列再多也白搭。
所以重点永远是:**让分组列+过滤列尽可能走索引,而不是堆砌所有 SELECT 字段**。
真正容易被忽略的是:索引列顺序一旦定下,后续新增 WHERE 条件或调整 GROUP BY 列顺序,很可能让原有索引彻底失效,而不是“降级使用”。上线前务必用真实数据量 EXPLAIN 验证。

















