2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 12 / 11 / 10 / 9.6

15.3. 并行计划 #

由于每个工作进程都会将计划的并行部分执行完毕,因此无法简单地采用一个普通的查询计划并用多个工作进程来执行。每个工作进程都会生成一份完整的输出结果集,因此查询不会比正常运行快,但会产生错误的结果。相反,计划的并行部分必须是查询优化器内部称为 部分计划 的东西;也就是说,它必须构造得使执行该计划的每个进程只生成输出行的子集,并且保证每一行所需的输出都由一个协作进程生成。通常,这意味着查询的驱动表上的扫描必须是支持并行的扫描。

15.3.1. 并行扫描 #

目前支持以下类型的并行扫描表。

  • 并行顺序扫描 中,表的块将被分成范围并由协作进程共享。每个工作进程在请求另一块范围之前将完成其给定块范围的扫描。

  • 并行位图堆扫描 中,会选择一个进程作为领导者。该进程执行一个或多个索引的扫描,并构建一个位图,指示需要访问哪些表块。然后,这些块将像在并行顺序扫描中一样在协作进程之间分配。换句话说,堆扫描是并行执行的,但底层的索引扫描不是。

  • 并行索引扫描并行仅索引扫描 中,协作进程轮流从索引读取数据。目前,仅对 btree 索引支持并行索引扫描。每个进程将声明一个单独的索引块,并扫描并返回该块引用的所有元组;其他进程可以同时从不同的索引块返回元组。并行 btree 扫描的结果在每个工作进程内以排序顺序返回。

其他扫描类型,例如非 btree 索引的扫描,将来可能会支持并行扫描。

15.3.2. 并行连接 #

与非并行计划一样,驱动表可以使用嵌套循环、哈希连接或合并连接连接到一个或多个其他表。连接的内侧可以是规划器支持的任何类型的非并行计划,前提是可以在并行工作进程中安全运行。根据连接类型,内侧也可以是并行计划。

  • 嵌套循环连接 中,内侧始终是非并行的。尽管它会被完整执行,但如果内侧是索引扫描,这是很高效的,因为外层元组以及因此查找索引值的循环被分散到协作进程中。

  • 合并连接 中,内侧始终是非并行计划,因此会完整执行。这可能效率低下,尤其是当必须执行排序时,因为工作和生成的数据会在每个协作进程中重复。

  • 哈希连接(不带“并行”前缀)中,内侧由每个协作进程完整执行以构建相同的哈希表副本。如果哈希表很大或计划很昂贵,这可能会效率低下。在 并行哈希连接 中,内侧是 并行哈希,它将构建共享哈希表的工作分配给协作进程。

15.3.3. 并行聚合 #

PostgreSQL 通过分两阶段进行聚合来支持并行聚合。首先,参与查询并行部分的每个进程执行一个聚合步骤,为该进程了解的每个组生成一个部分结果。这在计划中体现为 Partial Aggregate 节点。其次,部分结果通过 GatherGather Merge 传输给领导者。最后,领导者跨所有工作进程重新聚合结果以生成最终结果。这在计划中体现为 Finalize Aggregate 节点。

由于 Finalize Aggregate 节点在领导者进程上运行,因此与输入行数相比,产生相对较多组的查询对于查询规划器来说吸引力较小。例如,在最坏的情况下,Finalize Aggregate 节点看到的组数可能与所有工作进程在 Partial Aggregate 阶段看到的输入行数一样多。对于这种情况,使用并行聚合显然不会带来性能上的好处。查询规划器在规划过程中会考虑这一点,并且不太可能在这种情况下选择并行聚合。

并行聚合并非在所有情况下都受支持。每个聚合都必须 对并行安全 并且必须有一个组合函数。如果聚合的过渡状态类型为 internal,则它必须具有序列化和反序列化函数。有关更多详细信息,请参阅 CREATE AGGREGATE。如果任何聚合函数调用包含 DISTINCTORDER BY 子句,并且也不支持有序集聚合或查询涉及 GROUPING SETS,则不支持并行聚合。只有当查询中涉及的所有连接也是计划的并行部分的一部分时,才能使用它。

15.3.4. 并行追加 #

每当 PostgreSQL 需要将来自多个源的行合并到单个结果集中时,它就会使用 AppendMergeAppend 计划节点。这通常发生在实现 UNION ALL 或扫描分区表时。这些节点可以像在任何其他计划中一样在并行计划中使用。然而,在并行计划中,规划器可能会改用 Parallel Append 节点。

当在并行计划中使用 Append 节点时,每个进程将按出现顺序执行子计划,以便所有参与的进程协作执行第一个子计划直到完成,然后大致同时移至第二个计划。当改用 Parallel Append 时,执行器会将参与的进程尽可能均匀地分布到其子计划中,以便多个子计划同时执行。这可以避免争用,并且还可以避免那些从不执行它的进程支付子计划的启动成本。

此外,与只能在并行计划中使用时具有部分子节点的常规 Append 节点不同,Parallel Append 节点可以同时具有部分和非部分子节点。非部分子节点将仅由一个进程扫描,因为多次扫描它们会产生重复的结果。因此,即使在没有高效部分计划可用时,涉及追加多个结果集的计划也可以实现粗粒度并行。例如,考虑一个针对分区表的查询,该查询只能通过使用不支持并行扫描的索引来高效实现。规划器可能会选择常规 Index Scan 计划的 Parallel Append;每个单独的索引扫描都必须由一个进程完整执行,但是不同的扫描可以由不同的进程同时执行。

可以使用 enable_parallel_append 来禁用此功能。

15.3.5. 并行计划提示 #

如果一个预期会生成并行计划的查询没有生成并行计划,您可以尝试降低 parallel_setup_costparallel_tuple_cost。当然,这个计划可能比规划器偏好的串行计划慢,但这并不总是如此。即使使用非常小的值(例如,将两者都设置为零)也未获得并行计划,原因可能是查询规划器无法为您的查询生成并行计划。有关原因,请参阅 第 15.2 节第 15.4 节

在执行并行计划时,您可以使用 EXPLAIN (ANALYZE, VERBOSE) 来显示每个计划节点的每工作进程统计信息。这有助于确定工作是否在所有计划节点之间均匀分布,以及更普遍地了解计划的性能特征。

提交更正

如果您在文档中发现任何不正确、与您对特定功能的使用体验不符或需要进一步阐述的内容,请使用 此表单 报告文档问题。