国人提交PG 19“JOIN+聚合场景”优化补丁
PostgreSQL 19 preview - 国人提交“JOIN+聚合场景”优化补丁
前几天读到一篇关于"先groupby再JOIN"的数据库论文:
《AI论文解读 | Accelerating Queries With Groupby And Join By Group Join》
没想到今天在PG社区就看到类似场景的优化补丁了: Implement Eager Aggregation
这个PATCH 由 Antonin Houska 在 2017 年提出原型. 2025 Richard Guo(郭峰@OpenPie) 重构并重写了大部分代码,解决了正确性、性能和集成问题。经过多轮社区评审(Robert Haas、Tom Lane 等核心开发者参与),最终合并。
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=8e11859102f947e6145acdd809e5cdcdfbe90fa5
下面来看一下这个patch的详细解读 :
这个 PostgreSQL 补丁 “Implement Eager Aggregation”(实现“急切聚合”)由 Richard Guo 提交,是对查询优化器的一项重要增强。它引入了一种称为 Eager Aggregation(急切聚合) 的优化技术,旨在通过在连接(JOIN)之前提前执行部分聚合,减少中间数据量,从而提升查询性能。
下面从多个维度详细讲解该补丁的核心思想、实现机制、限制条件和意义。
一、什么是 Eager Aggregation?
Eager Aggregation 是一种查询优化策略:
在执行 JOIN 之前,尽可能早地对某个表或子查询进行部分聚合(partial aggregation),然后再参与后续的连接操作,最终在顶层完成完整的聚合。
举个例子:
原始查询:
SELECT a.x, SUM(b.y)
FROM a
JOIN b ON a.id = b.a_id
GROUPBY a.x;
传统执行计划:
先对 a和b做完整连接(可能产生大量中间行);再按 a.x分组并聚合SUM(b.y)。
启用 Eager Aggregation 后的优化计划:
先对 b按b.a_id做部分聚合(例如SUM(b.y) GROUP BY b.a_id);再将聚合后的 b与a连接;最后按 a.x做最终聚合(可能只需重分组,无需再处理大量原始行)。
效果:如果 b 中每个 a_id 对应大量行,提前聚合可显著减少连接输入规模。
二、为什么需要这个优化?
PostgreSQL 原有的优化器架构存在一个结构性限制:
扫描/连接规划阶段(scan/join planning)与聚合阶段是分离的。
聚合信息(如 GROUP BY、HAVING、聚合函数)在构建连接树时不可见,因此无法在连接前考虑是否可以提前聚合。
这导致优化器错失了通过提前聚合减少中间数据量的机会。
Eager Aggregation 通过在规划早期收集聚合上下文,并在连接规划阶段动态生成“已分组的关系(grouped relation)”,打破了这一限制。
三、关键技术实现
1. 聚合信息提前收集
在 PlannerInfo中记录:聚合函数(来自 SELECT 和 HAVING) GROUP BY 表达式 分组键(grouping keys)
这些信息在后续的连接规划中用于判断是否可应用 Eager Aggregation。
2. 引入“Grouped Relation”概念
对于某个基表或连接结果,如果满足条件,会创建一个新的 RelOptInfo,称为 grouped relation。它代表“已经按某些键部分聚合过”的版本。 为这个 grouped relation 生成新的执行路径(paths): Sorted Partial Aggregation Path(排序后聚合) Hashed Partial Aggregation Path(哈希聚合)
为控制规划开销,只对最便宜的或已排序的非分组路径生成分组路径。
3. 分组路径的生成方式
方式一:对单个基表/子查询直接做部分聚合。 方式二:将一个 grouped relation 与一个 non-grouped relation 进行连接(产生新的 grouped relation)。 不支持:两个 grouped relations 之间的连接(当前版本限制)。
4. 确保正确性的关键:扩展分组键
当对某个表做部分聚合时,必须将该表中用于后续 JOIN 的所有列加入分组键。
例如:
... FROM a JOIN b ON a.id = b.a_id AND a.status = b.status ...
若对 b 做部分聚合,则分组键必须包含 b.a_id 和 b.status,否则:
同一组内的多行可能在连接时匹配到不同的 a行;导致聚合结果错误(“destiny” 不一致)。
补丁通过分析上层 JOIN 条件,自动将相关列加入 partial GROUP BY。
5. 限制:不能用于 OUTER JOIN 的 nullable 侧
如果某表在 LEFT/RIGHT/FULL OUTER JOIN 的 nullable 一侧(如 LEFT JOIN 的右表),不能对其做 Eager Aggregation。 原因:OUTER JOIN 会生成 NULL 扩展行,这些行在顶层聚合时会影响结果(如 COUNT(*) 会包含 NULL 行)。 若提前聚合,这些 NULL 行尚未生成,会导致结果错误。
四、规划策略与性能权衡
保守策略:只在“能显著减少行数”的最低层级应用 Eager Aggregation。 避免生成过多备选路径,控制规划时间。 确保同一 grouped relation 的所有路径产生相同的结果集(满足优化器假设)。 最终,顶层的 grouped relation 会生成 finalized paths,与传统路径一起参与 cost 比较,选择最优计划。
五、历史背景与贡献
最初由 Antonin Houska 在 2017 年提出原型。 Richard Guo 重构并重写了大部分代码,解决了正确性、性能和集成问题。 经过多轮社区评审(Robert Haas、Tom Lane 等核心开发者参与),最终合并。
六、意义与影响
✅ 性能提升:对“大表连接后聚合”的场景(如星型模型、OLAP 查询)有显著加速。
✅ 架构改进:打破 scan/join 与 aggregation 的隔离,为未来更多聚合感知优化铺路。
⚠️ 谨慎启用:通过 cost 模型自动决策,无需用户干预,保证安全性和通用性。
总结
| 目标 | |
| 核心机制 | |
| 关键保障 | |
| 规划开销控制 | |
| 用户透明 |
这项补丁代表了 PostgreSQL 优化器向更智能、更上下文感知方向迈出的重要一步,尤其利好数据分析类负载。