group by 必须用复合索引,且列顺序须严格为 where 条件列 → group by 列 → order by 列 → 聚合字段;错序或隐式转换会导致索引失效,触发 using temporary 和 filesort。

索引本身不直接加速 GROUP BY,但能大幅减少分组前的数据扫描量和排序开销——关键在让数据库用索引完成“过滤 + 有序分组”,而非全表扫描后建临时表再排序。
GROUP BY 要用什么索引?
必须是复合索引,且列顺序严格遵循:WHERE 条件列 → GROUP BY 列 →(可选)ORDER BY 列 →(可选)聚合字段(用于覆盖索引)。例如:
- 查询
SELECT dept, COUNT(*) FROM emp WHERE status = 'active' GROUP BY dept,推荐索引:INDEX(status, dept) - 若还带
ORDER BY dept DESC,索引需明确方向:INDEX(status, dept DESC) - 若 SELECT 中还有
MAX(salary),且希望避免回表,可扩展为:INDEX(status, dept, salary)(覆盖索引)
错序索引(如建了 (dept, status) 却写 GROUP BY dept)大概率无法跳过排序,EXPLAIN 中会出现 Using temporary; Using filesort。
为什么函数或类型转换会让索引失效?
因为索引存储的是原始值,一旦对字段做运算,数据库无法用 B+ 树索引直接匹配:
-
WHERE YEAR(create_time) = 2023→ 索引create_time不生效 -
GROUP BY UPPER(name)→ 字符索引失效 -
WHERE user_id = '123'(user_id 是INT)→ 隐式转换导致索引失效
正确做法是改写为范围或显式类型一致:WHERE create_time >= '2023-01-01' AND create_time ,并确保字段类型与参数完全一致。
聚合字段(如 SUM、AVG)需要单独建索引吗?
不需要。对 amount 单独建索引对 SUM(amount) 几乎无用;真正起作用的是把 amount 放在复合索引末尾,构成覆盖索引,避免回表读取该字段。
- 有效:
INDEX(status, dept, amount)(支持WHERE status = ? GROUP BY dept+SUM(amount)) - 无效:
INDEX(amount)或INDEX(amount, status)(顺序错误,WHERE 条件无法利用)
例外是 MAX()/MIN():若只查单个极值,INDEX(amount) 可直接取 B+ 树端点,效率极高。
JOIN + GROUP BY 怎么避免爆炸式中间结果?
先聚合再 JOIN,而不是先 JOIN 再 GROUP BY。大表关联后分组,极易撑爆内存和临时表。
- 慢写法:
SELECT u.name, SUM(o.amount) FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id - 快写法(MySQL 派生表):
SELECT u.name, COALESCE(o.sum_amount, 0) FROM users u LEFT JOIN (SELECT user_id, SUM(amount) AS sum_amount FROM orders WHERE created_at >= '2026-07-01' GROUP BY user_id) o ON u.id = o.user_id
注意子查询中必须包含足够的 WHERE 过滤,并确保其 GROUP BY 字段顺序与索引一致——否则子查询本身又变慢了。
最易被忽略的一点:即使索引建对了,也要运行 ANALYZE TABLE(MySQL)或 VACUUM ANALYZE(PostgreSQL)更新统计信息,否则优化器可能因过期的行数估算而放弃走索引。










