核心原因是未走索引分组、强制回表和临时表落盘三重叠加;需确保联合索引按WHERE→GROUP BY→SELECT顺序构建,覆盖过滤、分组与查询字段,并避免GROUP BY中使用函数。

大宽表上 GROUP BY 查询慢,核心原因不是“字段多”,而是“没走索引分组 + 强制回表 + 临时表落盘”三重叠加——哪怕只有几十万行,Using temporary 和 Using filesort 一出现,查询就从毫秒跳到秒级甚至分钟级。
为什么EXPLAIN里显示用了索引,但GROUP BY还是慢?
MySQL 的索引对 GROUP BY 起效,必须满足两个硬条件:索引能覆盖 WHERE 过滤路径,且分组字段在索引中构成“连续最左前缀”,并能按顺序输出分组键(即支持 Loose Index Scan)。否则,即使 key 列显示用了索引,Extra 里只要出现 Using temporary,说明索引根本没帮上分组的忙。
- 常见误判:看到
type=ref就以为 OK,但没盯Extra——这是最常漏掉的诊断点 - 复合索引顺序错位:比如语句是
WHERE status = ? AND created_at > ? GROUP BY user_id,却建了INDEX(user_id, status),WHERE条件无法利用最左前缀,索引直接失效 - 索引宽度超标:宽表里 VARCHAR(255) 字段参与联合索引,
key_len显示 765,但实际只查前 10 个字符,说明索引定义和查询不匹配,优化器弃用
SELECT里混用非分组字段,为什么会让GROUP BY更慢?
MySQL 5.7 及更早版本允许 SELECT name, COUNT(*) FROM t GROUP BY id(name 非分组也非聚合),但底层会触发隐式 ANY_VALUE() + 随机取值逻辑;8.0+ 默认报错。无论哪个版本,只要 SELECT 包含未聚合的非分组字段,尤其是 TEXT/BLOB 类型或长字符串字段,就会强制回表读取完整行数据,大幅增加 IO 和内存压力。
- 回表放大效应:宽表单行超 2KB,10 万行分组结果就要额外读取 200MB 数据,远超 buffer pool 容量
- 内存溢出风险:
tmp_table_size和max_heap_table_size不足时,Using temporary会立刻退化为磁盘临时表,IO 成为瓶颈 - 正确写法:只
SELECT分组字段 + 聚合函数;如需关联信息,用JOIN后查小表,而非在聚合主表里拖着大字段走
大宽表上GROUP BY字段含函数,为什么等于放弃索引?
GROUP BY UPPER(name) 或 GROUP BY DATE(created_at) 这类写法,会让 MySQL 彻底放弃索引——因为索引 B+ 树里存的是原始值,不是函数计算后的结果,数据库只能全表扫描、逐行计算、再哈希/排序分组。这不是“可能慢”,是“必然触发全表扫描 + 内存排序”的确定行为。
- MySQL 5.7 不支持函数索引,
INDEX(DATE(created_at))是无效语法,建不成功 - 替代方案优先级:冗余字段(如
created_date DATE)> 范围过滤(WHERE created_at >= '2026-09-01')> 应用层聚合 - 特别注意:时间字段上建了
INDEX(created_at),但写成GROUP BY YEAR(created_at),索引完全无用,且 YEAR() 计算本身开销不小
真正卡住性能的,往往不是分组逻辑本身,而是执行计划里那个被忽略的 Using temporary ——它意味着你正在用内存或磁盘硬扛整个中间结果集。宽表场景下,少一个回表字段、少一次函数计算、索引顺序调对一位,都可能把 12 秒查询压到 300ms。别猜,先 EXPLAIN FORMAT=TRADITIONAL,盯死 Extra 和 rows。

















