优化GROUP BY需依托索引避免临时表和filesort:建复合索引(WHERE字段在前、GROUP BY字段在后),确保顺序匹配最左前缀,用EXPLAIN检查Extra是否含Using temporary或Using filesort。

GROUP BY 本身不提速,真正起效的是让数据库能跳过排序和临时表——关键看它有没有索引可依、数据能不能提前砍掉、分组逻辑是不是可降维。
EXPLAIN 里出现 Using temporary 或 Using filesort 就得立刻停手
这两个提示是性能崩塌的明确信号:MySQL 正在用磁盘临时表做分组,而不是靠索引或内存哈希。它们通常同时出现,意味着查询已脱离可控范围。
- 先跑
EXPLAIN FORMAT=TRADITIONAL SELECT ... GROUP BY ...,紧盯Extra列 -
type是ALL或index(而非ref/range)说明没走有效索引 -
key为空或不是你建的索引名,大概率索引没被选中
复合索引必须严格匹配 GROUP BY 字段顺序
MySQL 只认“最左前缀匹配”,且顺序错一位,整个索引就废了。它不是在找字段,是在找一个能直接按序扫描的路径。
- 查
SELECT dept_id, COUNT(*) FROM emp GROUP BY dept_id→ 索引建(dept_id) - 查
SELECT dept_id, status, AVG(salary) FROM emp GROUP BY dept_id, status→ 索引必须是(dept_id, status),不能反过来 - 如果
SELECT还带了非聚合字段如manager_name,索引得扩成(dept_id, status, manager_name),否则仍会回表甚至退化
WHERE 条件必须塞进索引最左侧
过滤越早,参与分组的数据越少——但前提是这个过滤能和分组一起被索引覆盖。把 WHERE 字段丢在索引右边,等于白搭。
- 查
SELECT city, COUNT(*) FROM user WHERE reg_date >= '2024-01-01' GROUP BY city - 索引应建为
(reg_date, city),不是(city, reg_date) - 若还常按
status = 'active'过滤,索引优先考虑(status, reg_date, city)
高基数字段分组是隐形炸弹
对 user_id(尤其是 UUID)、email、ip 这类唯一值极多的字段直接 GROUP BY,极易触发内存溢出,强制写磁盘临时表。
- 别写
GROUP BY CONCAT(LEFT(email, 2), '@', SUBSTRING_INDEX(email, '@', -1))—— 函数分组彻底废索引 - 想按邮箱域名统计?先加生成列:
ALTER TABLE user ADD COLUMN domain VARCHAR(64) AS (SUBSTRING_INDEX(email, '@', -1)) STORED,再建索引(domain) - 要算去重数?
COUNT(DISTINCT user_id)比COUNT(*)开销高得多;业务允许时改用APPROX_COUNT_DISTINCT()(MySQL 8.0+)
最难缠的从来不是语法对不对,而是你以为自己在分组,其实数据库正在默默往磁盘上 dump 临时文件——那个瞬间,tmp_table_size 和 max_heap_table_size 的配置值,比 SQL 写法更决定成败。

















