group by 慢的关键在于执行计划出现 using temporary 或 using filesort,主因是索引列顺序不匹配查询模式(where→group by→order by),或对分组字段使用函数导致索引失效。

GROUP BY 为什么慢?关键看执行计划里有没有 Using temporary 和 Using filesort
这两个提示是性能瓶颈的明确信号:数据库被迫建临时表、额外排序,而不是直接利用索引顺序分组。根本原因不是 GROUP BY 本身不能走索引,而是它依赖数据在物理或逻辑上的有序性——而索引(尤其是 B+ 树)天然提供这种有序性。只要索引列顺序匹配查询模式,就能跳过排序和临时表。
索引列顺序必须严格按 WHERE → GROUP BY → ORDER BY 排列
比如这条慢查询:SELECT dept, COUNT(*) FROM emp WHERE status = 'active' GROUP BY dept ORDER BY COUNT(*) DESC,最有效的索引是 INDEX(status, dept),而不是 INDEX(dept, status) 或单独的 INDEX(dept)。
-
status放最左:先快速过滤掉 95% 的 inactive 数据,减少后续分组基数 -
dept紧跟其后:过滤后的结果天然按 dept 有序,GROUP BY 可流式分组,不触发Using filesort - 如果还有
ORDER BY dept,该索引仍有效;但若ORDER BY COUNT(*),就无法避免排序,此时应考虑业务是否真需实时排序,或改用物化视图预计算
别在 GROUP BY 字段上用函数,否则索引直接失效
常见错误:GROUP BY DATE(create_time)、GROUP BY UPPER(name)、GROUP BY CONCAT(first_name, ' ', last_name)。这些写法会让 MySQL 放弃使用 create_time 或 name 上的任何索引,退化为全表扫描 + 临时表。
- 替代方案一:加生成列并索引,如
ALTER TABLE logs ADD COLUMN create_date DATE AS (DATE(create_time)) STORED,再建索引INDEX(create_date, user_id) - 替代方案二:用范围条件缩小数据量,如把
GROUP BY DATE(create_time)拆成WHERE create_time >= '2026-08-01' AND create_time (配合 <code>INDEX(create_time)) - MySQL 8.0+ 不支持函数索引,PostgreSQL 可用
CREATE INDEX idx_func ON t ((UPPER(name))),但要注意维护成本
覆盖索引能省掉回表,但字段顺序和类型必须严丝合缝
例如查询 SELECT user_id, SUM(amount) FROM orders WHERE status = 1 GROUP BY user_id,理想索引是 INDEX(status, user_id, amount)。注意三点:
- 字段顺序不能错:
user_id必须在status之后,否则无法用于分组有序性 -
amount要放最后:它只用于聚合计算,不参与过滤或分组,放末尾不影响最左前缀匹配 - 类型必须一致:如果
user_id是BIGINT,但 WHERE 中传的是字符串'123',隐式转换会让索引失效——查EXPLAIN的type字段,如果是ALL或index就要警惕
真正容易被忽略的是统计信息过期和 NULL 值处理:即使索引建对了,ANALYZE TABLE 没跑过,优化器可能误判行数而选错执行计划;而 WHERE dept IS NOT NULL 有时能激活索引,因为 NULL 值在 B+ 树中位置不固定,影响范围扫描效率。










