group by本身不耗cpu,但缺乏索引会导致mysql强制执行全内存/磁盘排序与聚合计算,引发cpu飙升;典型表现为explain出现using temporary和using filesort,需建覆盖where、group by及select非聚合字段的最左前缀联合索引。

GROUP BY 本身不耗CPU,但缺乏索引支撑的 GROUP BY 会强制 MySQL 执行全内存/磁盘排序 + 聚合计算,这才是 CPU 飙升的直接原因。
GROUP BY 触发临时表和文件排序的典型场景
MySQL 在执行 GROUP BY 时,如果无法利用索引完成分组(即“松散索引扫描”或“紧凑索引扫描”不可用),就会走 Using temporary; Using filesort 路径。这意味着:
- 每行数据先被读入内存临时表(
tmp_table_size限制内)或落盘临时表(max_heap_table_size超限后) - 然后对临时表按 GROUP BY 字段做完整排序(哪怕你没写
ORDER BY) - 最后逐组扫描、累加聚合值——这个过程全是 CPU 密集型操作
例如:SELECT user_id, COUNT(*) FROM orders WHERE create_time > '2026-09-01' GROUP BY user_id,若 (create_time, user_id) 上无复合索引,MySQL 就得先过滤出几万行,再全部排序分组。
为什么 EXPLAIN 显示 type=ALL 或 rows 很大就危险
当 EXPLAIN 中出现以下任一情况,GROUP BY 极可能吃光 CPU:
-
type是ALL或index(全索引扫描),说明没走有效过滤索引 -
rows值远大于最终结果行数(比如扫 50 万行,只返回 200 组),说明大量无效数据参与了排序和聚合 -
Extra列含Using temporary和/或Using filesort—— 这是 CPU 狂飙的明确信号
注意:即使 key 显示用了某个索引,但如果该索引不覆盖 WHERE 过滤条件 + GROUP BY 字段 + SELECT 中的非聚合字段,仍会回表或建临时表。
复合索引怎么建才真正生效
GROUP BY 的索引不是随便拼字段就行,必须严格匹配执行路径。关键规则:
- 最左前缀必须覆盖
WHERE条件字段(等值查询优先),再接GROUP BY字段;例如WHERE status = 'PAID' AND create_time > '2026-09-01' GROUP BY user_id,索引应为(status, create_time, user_id) - 如果
SELECT包含非聚合字段(如SELECT user_id, MAX(amount)),该字段也需包含在索引末尾,才能触发“覆盖索引”,避免回表 -
ORDER BY若与GROUP BY字段一致(如GROUP BY user_id ORDER BY user_id),可复用同一索引;但若方向不一致(ASCvsDESC),MySQL 8.0+ 才支持混合排序,旧版本仍可能触发 filesort
反例:INDEX(user_id) 对 WHERE create_time > ... GROUP BY user_id 几乎无效——因为范围查询字段不能跳过,user_id 在索引中不在最左且无法用于过滤。
CPU 飙升时最容易被忽略的两个点
一是 tmp_table_size 和 max_heap_table_size 设得太小,导致本可在内存完成的分组被迫写磁盘,IO + CPU 双重压力;二是 GROUP BY 语句被高频调用(比如每秒几十次的统计看板轮询),单次不慢,累计 CPU 时间片却占满核心。这两点在监控里不会直接报错,但查 SHOW GLOBAL STATUS LIKE 'Created_tmp%' 和 performance_schema.events_statements_summary_by_digest 就能暴露真相。











