SQL 中 GROUP BY 查询很慢,应该如何排查

蓝鲸
蓝鲸
发布时间:2026-08-22 | 54人浏览过
原创

前言

遇到 GROUP BY 查询变慢时,很多优化都停留在“补个索引试试”这一层,结果往往不稳定。更有效的做法,是先确认执行计划是否落入临时表和额外排序,再回头检查联合索引顺序、覆盖情况以及 SQL 写法本身是否给优化器制造了障碍;看完你就能判断问题到底出在执行路径、索引设计,还是语义选择上。

很多人一看到 GROUP BY 变慢,第一反应是“数据量太大”,但实际更常见的问题是执行路径跑偏:该走索引的地方没走,最后落到 Using temporary 或 Using filesort。这篇文章就按排查顺序来拆解,先教你怎么从 EXPLAIN 一眼判断病根,再看“明明建了索引却还是慢”的典型失效场景,最后给出 DISTINCT 替代和联合索引排序的判断方法。

先看 EXPLAIN:GROUP BY 是否已经触发临时表或排序

排查 GROUP BY 性能,第一步不是急着改 SQL,而是先看执行计划。直接运行:

用 EXPLAIN 判断 GROUP BY 是否触发临时表和额外排序的诊断信息图
GROUP BY 执行计划的4个危险信号先看 EXPLAIN 的 Extra 和 type,能快速判断 GROUP BY。
EXPLAIN FORMAT=TRADITIONAL

重点盯住 Extra 列,它通常比“有没有命中索引”更能说明问题。

出现 Using temporary,通常就是性能断崖

  • Using temporary:说明 MySQL 先把匹配行拉进内存或磁盘临时表,再做分组,这是最典型的慢查询信号。
  • 这类执行路径意味着分组过程没有直接复用索引顺序,而是额外做了一次中间结果整理。

出现 Using filesort,说明排序逻辑没复用索引

  • Using filesort:即便 GROUP BY 字段本身建了索引,只要排序步骤不能复用索引顺序,MySQL 仍然会强制排序。
  • 典型情况就是后面又跟了 ORDER BY COUNT(*) DESC,这类排序无法直接沿着普通索引顺序完成。

别只看 key,还要结合 type 和 Extra 一起判断

  • key 显示用了索引,但 Extra 里仍有 Using temporary 或 Using filesort,基本等于索引“挂上了,但没真正帮到分组”。
  • type 如果是 ALL 或 index,而不是 ref / range,通常说明 WHERE 条件本身就没有效利用索引,后面的 GROUP BY 更难快起来。

为什么建了索引,GROUP BY 还是慢

最常见的误区,是给 GROUP BY 字段单独建一个索引,就以为问题解决了。实际执行时,索引是否有效取决于顺序、覆盖性以及 WHERE 条件是否匹配。

索引顺序不对,单列索引和错序索引都可能失效

  • 如果 SQL 是 GROUP BY a, b,但索引建成 (b, a) 或只建了 (a),就无法按预期支持分组。
  • B+ 树索引不能跳过前缀,直接按后缀字段完成分组,这也是“有索引却没提速”的高频原因。

SELECT 没覆盖,回表会把 IO 成本拉高

例如:

SELECT a, b, SUM(c) FROM t GROUP BY a, b

如果索引只有 (a, b),那么 c 仍然需要回表读取。数据量一大,随机 IO 成本会明显上升,分组本身再快也会被拖慢。

WHERE 条件破坏最左前缀,前面的索引设计等于白做

例如:

WHERE status = 1 AND created_at > '2024-01-01'

如果索引是:

(user_id, status, created_at)

而 user_id 没出现在 WHERE 中,那么最左前缀就断了,后面的 status、created_at 也很难按预期发挥作用。

隐式类型转换,也会让索引直接失效

例如:

WHERE user_id = '123'

如果字段类型其实是 BIGINT,就可能触发隐式类型转换,导致索引失效,覆盖索引也随之失去意义。这类问题在排查时很容易被忽略,但在实际线上环境并不少见。

什么时候 DISTINCT 可能比 GROUP BY 更快

如果你的目标只是“列出所有满足条件的组合”,并不需要聚合结果,例如没有 COUNT(*)、SUM() 之类的计算,那么可以认真比较一下 DISTINCT。

常见对比写法如下:

SELECT DISTINCT customer_id, city FROM orders WHERE ...
SELECT customer_id, city FROM orders WHERE ... GROUP BY customer_id, city
  • 在 MySQL 和 SQL Server 中,DISTINCT 往往更倾向走 Sort + Unique。
  • 而 GROUP BY 可能触发哈希聚合或临时表,执行代价不一定更低。

但 DISTINCT 不能无脑替换 GROUP BY

  • 如果后续还要加 HAVING 或聚合函数,就不能简单替换成 DISTINCT。
  • 两者在语法层面可能看起来“等价”,但执行引擎底层路径往往完全不同,是否更快必须结合执行计划判断。

联合索引怎么排,才更适合 GROUP BY

联合索引不是把字段简单堆进去就行,顺序决定了它能不能真正减少扫描、排序和回表。

联合索引顺序与 GROUP BY 加速关系的信息图
GROUP BY 的联合索引排序原则联合索引要按过滤、分组、覆盖的顺序设计,才能同时减少扫描、排序和回表。

一个实用顺序:WHERE → GROUP BY → SELECT

  • 通常可按 WHERE → GROUP BY → SELECT 的顺序来组织联合索引。
  • WHERE 中的等值过滤字段优先放前面,先缩小扫描范围,再让 GROUP BY 复用后续索引顺序。
  • 如果查询里还有非聚合查询列,需要尽量纳入索引,减少回表。

范围条件的位置,要结合后续分组字段一起看

  • 像 created_at > '2024-01-01' 这样的范围条件,通常要放在 WHERE 中高区分度字段之后。
  • 一旦范围扫描过早截断索引顺序,后续字段能否继续用于分组和覆盖,就要非常谨慎地评估。

一个直接可用的索引设计示例

例如查询条件是:

WHERE status IN ('A','B') AND created_at > '2024-01-01' GROUP BY user_id, product_type

那么更合适的索引可以是:

(status, created_at, user_id, product_type)

如果还要查询:

SELECT MAX(amount)

那么可以继续把 amount 放到索引末尾,SQL Server 可使用 INCLUDE,MySQL 则作为普通索引列加入,以尽量形成覆盖。

最后判断标准:Extra 才是最可信的线索

真正拖慢 GROUP BY 的,很多时候不是数据量本身,而是索引没有对上执行路径。排查时不要只看“有没有建索引”,而要回到 EXPLAIN 的 Extra、type、key 这些核心字段上:只要 Using temporary、Using filesort 还在,优化通常就还没做到位。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表