SQL中GROUP BY的工作原理(详解分组执行流程、排序与哈希算法及HAVING用法)

SQL 的 GROUP BY 把多行数据按指定列折叠成”一组一行”的汇总结果,是分组统计的核心子句。它的底层执行分两步:先用 WHERE 过滤行,再把数据按分组键排序或哈希归拢成桶,最后对每个桶跑聚合函数。据 ProgrammingLine 的解析,引擎先确定 WHERE 保留的行,再按 GROUP BY 列把它们分进不同的”桶”,每个桶只产出一行结果。理解这一机制,也就理解了为何 SELECT 中的列要么进 GROUP BY、要么被聚合函数包裹——折叠后组内已无单一确定的非聚合值。

一、GROUP BY 在做什么:行的折叠

GROUP BY 的本质是集合划分:按分组列的相同取值,把原表行归并为子集,每个子集(组)输出一行。它几乎总是和聚合函数(COUNT/SUM/AVG/MIN/MAX)搭配,因为正是聚合函数负责算出每组的那个汇总值。

据 leyaa.ai 的”执行流”图示,一条带 GROUP BY 的查询依次经历:

原始行 → WHERE(过滤行)→ GROUP BY(排序/哈希分组)
      → Aggregation(组内聚合)→ HAVING(过滤组)→ 输出(每组一行)

一个反直觉的关键点:GROUP BY 不会保留原行,它返回的是”每个组一行”,而非每行一行。CSDN 与数据库系统原理文章都强调,这正是新手常犯的误解——以为它只是排序后原样返回。

二、底层两大分组算法

优化器据数据规模、可用内存和索引情况,在两种算法间择一(综合 CSDN 阿里云开发者社区、leyaa.ai、数据库系统原理):

算法 触发条件 核心过程 特点
排序分组(Sort) 分组列有索引,或优化器认为排序更优 按分组列排序,使同组行相邻,顺序扫描累加 结果天然有序;但排序约 O(N log N),内存占用小,适合大数据
哈希分组(Hash) 无合适索引可用 计算分组键哈希,把行放进哈希桶,桶内聚合 内存充足时接近 O(N),更快;但输出顺序不确定,需显式 ORDER BY

索引能改变算法。当分组列上存在有序索引时,引擎可直接沿索引顺序”发现组边界变化”就输出,无需显式排序或临时表——这正是 CSDN 文章中”加联合索引后 Using temporary/Using filesort 同时消失”的原因。

三、MySQL 的临时表流程(默认路径)

当没有可用索引时,MySQL 典型执行流程(CSDN 详解)为:

  1. 创建内存临时表,字段为分组列和聚合结果列。
  2. 全表(或索引)扫描,逐行取出分组键值。
  3. 临时表中已有该键 → 更新聚合值;没有 → 插入新行。
  4. 遍历完后,按分组列排序并返回。

这个默认路径既用临时表又用排序,因此容易变慢。若内存临时表达到 tmp_table_size 上限,会转成磁盘临时表,性能显著下降。

四、HAVING:在组上再过滤

WHERE 过滤行(分组前),HAVING 过滤组(聚合后),二者执行顺序固定。据 CSDN 的”同时含 WHERE/GROUP BY/HAVING”示例,完整顺序是:WHERE 选行 → GROUP BY 分组 → 聚合计算 → HAVING 筛组。

sql

SELECT city, COUNT(*) AS num
FROM staff
WHERE age > 19
GROUP BY city
HAVING num >= 3;

HAVING 是唯一能引用聚合结果(如 COUNT(*) > 1)的地方;WHERE 中写聚合函数会直接报错。

五、NULL 也参与分组

据 leyaa.ai 的 MySQL 深度解析,GROUP BY 把 NULL 当作一个独立组值,所有 NULL 行归入同一组,而非被忽略。这与 COUNT(col) 忽略 NULL 不同——分组统计里 NULL 会单独占一个桶,统计总数时不可漏算。

六、两个必须知道的约束

  • SELECT 列约束:非聚合列必须出现在 GROUP BY 中,否则 PostgreSQL、MySQL 严格模式直接报 only_full_group_by 错误;SQLite 虽宽松但会随机取组内一行,是隐蔽 bug 源。
  • 列顺序不影响分组语义:GROUP BY A, B 与 GROUP BY B, A 分出的组完全相同,列序只影响排序分组的内部处理顺序。这与 ORDER BY 截然不同。

七、GROUP BY 的优化方向

据 CSDN 优化章节,主要手段有四个:

  1. 分组列加索引:让引擎走有序索引直接分组,省去临时表与排序。
  2. ORDER BY NULL:若不需要结果排序,去掉默认排序以消除 filesort。
  3. 尽量只用内存临时表:调大 tmp_table_size,避免落入磁盘临时表。
  4. SQL_BIG_RESULT 提示:预估大数据量时,直接用磁盘临时表并以数组存储,避免内存转磁盘的反复。

八、何时不该用 GROUP BY

leyaa.ai 指出:当你需要逐行明细、或因窗口函数更适合的场景(累计求和、组内排名)时,应改用窗口函数。GROUP BY 会折叠行,无法同时保留明细与汇总;这类”分区内计算但不折叠”的需求,窗口函数是更正确的选择。

常见问题(FAQ)

GROUP BY 列的顺序影响分组结果吗?
不影响。GROUP BY A,B 与 GROUP BY B,A 分出的组完全相同,列序只影响内部排序处理。

NULL 值会被 GROUP BY 忽略吗?
不会。NULL 被单独归为一个组,统计时需计入,与 COUNT(col) 忽略 NULL 是两回事。

为什么 SELECT 里不能随便写列?
折叠后组内无单一确定的非聚合值,标准 SQL 要求非聚合列必须进 GROUP BY,否则报错或结果不可控。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
蓝队管理员的头像蓝队管理员普通用户

相关推荐

返回顶部