GROUP BY卡住主因是执行计划出现Using temporary,导致磁盘临时表IO激增;需确保WHERE+GROUP BY字段联合索引覆盖、避免函数索引失效、优先过滤数据、禁用隐式排序,并定期更新统计信息。

为什么GROUP BY一跑就卡住?先看执行计划里有没有Using temporary
卡住不是因为SQL写错了,而是数据库被迫用磁盘临时表做分组。MySQL在EXPLAIN里一旦出现Using temporary(有时还带Using filesort),基本等于宣告:它没走索引分组,正在内存里建哈希表,撑不住就刷到磁盘——IO一上来,10秒变100秒。
-
Using temporary出现的常见原因:GROUP BY字段没索引、索引顺序不匹配、WHERE条件用了函数导致索引失效(比如WHERE DATE(created_at) = '2026-08-01') - 别只盯着
GROUP BY列建索引——要把WHERE过滤字段放前面,再接GROUP BY字段,例如:WHERE status = 1 AND region = 'east' GROUP BY user_id, product_type→ 索引应为(status, region, user_id, product_type) - 如果
SELECT里只查分组键和聚合值(如COUNT(*)、SUM(amount)),且索引覆盖全部字段,就能触发“覆盖索引”,连表数据都不用读
MySQL 8.0+ 用GROUP BY还是DISTINCT?实测差距能到10倍
当目标只是去重计数(比如“有多少个活跃用户”),SELECT COUNT(DISTINCT user_id)或SELECT DISTINCT user_id往往比GROUP BY user_id快得多——尤其在分组键基数低、重复率高的场景下。
-
GROUP BY默认会做隐式排序(即使没写ORDER BY),MySQL 5.7默认启用sql_mode里的ONLY_FULL_GROUP_BY,更强化了排序行为;而DISTINCT只要求唯一性,引擎可跳过排序步骤 - 若必须用
GROUP BY(比如要同时SUM(revenue)和COUNT(*)),加ORDER BY NULL能显式禁用排序:GROUP BY user_id ORDER BY NULL - 注意:
DISTINCT不能替代所有GROUP BY场景——它不支持对每个分组做多维度聚合(如AVG()、MAX()混用),也不能配合HAVING做过滤
大表分组前,先用WHERE把数据砍掉90%
优化GROUP BY最廉价的方式,不是调参数,是少喂数据。很多慢查询的根源,是先扫2000万行,再从中分组——哪怕索引再好,扫描成本已定。
- 优先用高选择性条件过滤:比如
WHERE created_at >= '2026-08-01'比WHERE status IN (1,2,3)更有效(假设日增量仅5万) - 分区表不是银弹,但对时间/地域类字段有效:若按
date分区,WHERE date BETWEEN '2026-08-01' AND '2026-08-03'能直接裁剪掉95%分区 - 避免在
WHERE里用OR连接多个范围条件——优化器常放弃索引走全表;改用UNION ALL拆成独立子查询,每个都能走索引
别碰tmp_table_size和max_heap_table_size盲目调大
这两个参数控制内存临时表上限,但调高≠解决卡顿,反而可能引发OOM或挤占其他查询资源。
- 单次查询内存临时表最大只能用到
min(tmp_table_size, max_heap_table_size),设成2G不代表每次都能用满——它受全局并发限制,且每个连接独享一份 - 真正瓶颈常在磁盘IO:一旦临时表溢出,
Created_tmp_disk_tables计数飙升,说明已在刷磁盘;此时调内存参数只是延缓问题,不如先压数据量或换索引 - 监控关键指标:
SHOW GLOBAL STATUS LIKE 'Created_tmp%';如果Created_tmp_disk_tables / Created_tmp_tables > 0.2,说明至少20%的临时表已落盘,该动手优化了
最容易被忽略的是:GROUP BY性能拐点不在千万级,而在统计信息过期那一刻。哪怕索引完美,只要ANALYZE TABLE半年没跑,优化器估算的行数偏差10倍,就可能选错执行路径——它以为能内存哈希,实际被迫排序。定期更新统计信息比调任何参数都管用。


















