2024年9月26日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 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)显示每个计划节点的每个工作进程统计信息。这可能有助于确定工作是否在所有计划节点之间均匀分配,以及更普遍地了解计划的性能特征。

提交更正

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