优化GROUP BY的核心是避免Using temporary和Using filesort,关键在于创建覆盖WHERE条件、GROUP BY字段及SELECT非聚合字段的复合索引,且索引列顺序须严格匹配,范围条件会中断最左前缀,函数包裹字段需MySQL 8.0.13+函数索引支持。

GROUP BY 出现 Using temporary 或 Using filesort 就是索引没用上
这不是数据量大导致的,而是 MySQL 没法利用索引的有序性做分组。它被迫把所有匹配行读出来,再建临时表、排序、哈希分组——I/O 和内存开销直接翻倍。
查执行计划:EXPLAIN SELECT dept, COUNT(*) FROM user WHERE status = 1 GROUP BY dept,如果 Extra 列出现 Using temporary 或 Using filesort,说明索引失效了。
- 建错索引顺序:比如 WHERE 是
status = 1,GROUP BY 是dept,却建了(dept, status)—— 最左前缀不匹配,status根本用不上 - WHERE 里用了范围条件(如
time > '2026-01-01')放在联合索引最左:它会截断索引,后面字段全失效 - GROUP BY 字段被函数包裹:
GROUP BY UPPER(dept)或GROUP BY DATE(created_at),除非建函数索引(MySQL 8.0.13+),否则索引白搭
聚合前没用 WHERE 过滤,反而靠 HAVING 筛结果
HAVING 是在所有分组计算完之后才执行的“事后筛子”。哪怕你只想保留 COUNT(*) > 100 的组,数据库也得先把几百万行分好组、算完数、写进临时表,最后才扔掉 99% 的组。
而 WHERE 是在扫描阶段就拦住无效行——能减少 90% 以上 I/O 和 CPU 开销。
- 时间范围、状态码、类型标识等强筛选条件,必须写在
WHERE里,例如WHERE rq >= '2026-02-01' AND status = 1 - 避免
HAVING COUNT(*) > 10这类写法;真要过滤聚合结果,优先考虑能否提前用子查询或物化视图降维 -
SELECT name, COUNT(*) FROM user GROUP BY dept在严格模式下直接报错(name既没在 GROUP BY 也没套聚合函数),宽松模式下强制走临时表
关联后聚合比先聚合再关联慢十倍
优化器经常误判 SQL 语义,把 SELECT ... GROUP BY ... JOIN ... 理解成“先 JOIN 再 GROUP BY”,结果把千万级明细和维度表拼出上亿行中间结果,最后才分组。
典型症状:子查询单独跑 1 秒,加上 INNER JOIN 后涨到 30 秒,EXPLAIN 显示 rows_examined 暴增。
- 改用
LEFT JOIN(即使业务逻辑等价),优化器更倾向保留左表驱动顺序 - 确保关联字段字符集和排序规则完全一致(如都是
utf8mb4_0900_ai_ci),混用会触发隐式转换,索引失效 - 维度表缺主键或唯一约束时,优化器不敢下推聚合,补上
PRIMARY KEY或UNIQUE能显著改善
COUNT(*) 本身不慢,慢在没走索引或引擎特性
InnoDB 不像 MyISAM 那样缓存行数,每次 COUNT(*) 都得扫一遍数据页。但真正拖慢的不是函数本身,而是访问路径。
如果只统计全表行数且无 WHERE 条件,COUNT(*) 会走最小的可用索引(通常是主键),但一旦加了条件,没索引就只能全表扫描。
- 有 WHERE 条件时,给过滤字段建索引,比纠结用
COUNT(*)还是COUNT(id)重要得多 - 高频统计场景(如日活、订单总数),别依赖实时聚合,用预计算表 + 应用层更新更稳
-
COUNT(DISTINCT col)在 MySQL 8.0+ 支持松散索引扫描,比SELECT COUNT(*) FROM (SELECT DISTINCT col FROM t) _快得多


















