由于每个工作进程都会完整地执行计划的并行部分,因此不可能简单地采用一个普通的查询计划并使用多个工作进程来运行它。每个工作进程都会生成输出结果集的完整副本,因此查询的运行速度不会比正常情况快,但会产生不正确的结果。相反,计划的并行部分必须是查询优化器内部称为部分计划的内容;也就是说,它的构造必须使得执行该计划的每个进程仅生成输出行的子集,并保证每个必需的输出行都恰好由一个协作进程生成。通常,这意味着查询驱动表上的扫描必须是并行感知的扫描。
当前支持以下类型的并行感知表扫描。
在并行顺序扫描中,表的块将被划分为多个范围,并在协作进程之间共享。每个工作进程将完成其给定范围的块的扫描,然后才会请求额外的块范围。
在并行位图堆扫描中,选择一个进程作为领导者。该进程执行一个或多个索引的扫描,并构建一个位图,指示需要访问的表块。然后,这些块像在并行顺序扫描中一样在协作进程之间划分。换句话说,堆扫描是并行执行的,但底层索引扫描不是。
在并行索引扫描或并行仅索引扫描中,协作进程轮流从索引读取数据。当前,并行索引扫描仅支持 btree 索引。每个进程将声明一个索引块,并扫描并返回该块引用的所有元组;其他进程可以同时返回来自不同索引块的元组。并行 btree 扫描的结果在每个工作进程中按排序顺序返回。
其他扫描类型,例如非 btree 索引的扫描,将来可能会支持并行扫描。
就像在非并行计划中一样,驱动表可以使用嵌套循环、哈希连接或合并连接连接到一个或多个其他表。连接的内侧可以是规划器支持的任何类型的非并行计划,只要它可以在并行工作进程中安全运行即可。根据连接类型,内侧也可以是并行计划。
在嵌套循环连接中,内侧始终是非并行的。虽然它是完整执行的,但如果内侧是索引扫描,这会很高效,因为外部元组以及查找索引中值的循环会被分配给协作进程。
在合并连接中,内侧始终是非并行计划,因此会完整执行。这可能效率低下,尤其是在必须执行排序时,因为工作和生成的数据在每个协作进程中都会重复。
在哈希连接(没有“并行”前缀)中,内侧由每个协作进程完整执行,以构建哈希表的相同副本。如果哈希表很大或计划开销很大,这可能效率低下。在并行哈希连接中,内侧是并行哈希,它将构建共享哈希表的工作分配给协作进程。
PostgreSQL 支持通过两个阶段进行聚合的并行聚合。首先,参与查询并行部分的每个进程都会执行聚合步骤,为该进程知道的每个组生成部分结果。这在计划中反映为一个 Partial Aggregate
节点。其次,部分结果通过 Gather
或 Gather Merge
传输到领导者。最后,领导者重新聚合所有工作进程的结果,以生成最终结果。这在计划中反映为一个 Finalize Aggregate
节点。
由于 Finalize Aggregate
节点在领导进程上运行,因此与输入行数相比,产生相对较大组数的查询对于查询规划器而言不太有利。例如,在最坏的情况下,Finalize Aggregate
节点看到的组数可能与所有工作进程在 Partial Aggregate
阶段看到的输入行数一样多。对于这种情况,使用并行聚合显然不会有性能优势。查询规划器在规划过程中会考虑到这一点,并且不太可能在这种情况下选择并行聚合。
并非在所有情况下都支持并行聚合。每个聚合都必须对并行性是安全的,并且必须具有组合函数。如果聚合具有类型为 internal
的转换状态,则它必须具有序列化和反序列化函数。有关详细信息,请参见 CREATE AGGREGATE。如果任何聚合函数调用包含 DISTINCT
或 ORDER BY
子句,并且对于有序集聚合或查询涉及 GROUPING SETS
时,则不支持并行聚合。仅当查询中涉及的所有连接也属于计划的并行部分时,才能使用并行聚合。
每当 PostgreSQL 需要将来自多个源的行合并到单个结果集中时,它都会使用 Append
或 MergeAppend
计划节点。当实现 UNION ALL
或扫描分区表时,通常会发生这种情况。此类节点可以在并行计划中使用,就像它们可以在任何其他计划中使用一样。但是,在并行计划中,规划器可能会改为使用 Parallel Append
节点。
当在并行计划中使用 Append
节点时,每个进程将按照它们出现的顺序执行子计划,以便所有参与进程合作执行第一个子计划直到它完成,然后在大约同一时间移动到第二个计划。当改为使用 Parallel Append
时,执行程序会将参与进程尽可能均匀地分散到其子计划中,以便同时执行多个子计划。这避免了争用,并且还避免了那些从不执行子计划的进程支付子计划的启动成本。
此外,与只能在并行计划中使用时具有部分子级的常规 Append
节点不同,Parallel Append
节点可以同时具有部分和非部分子计划。非部分子计划将仅由单个进程扫描,因为多次扫描它们会产生重复的结果。因此,即使没有高效的部分计划可用,涉及追加多个结果集的计划也可以实现粗粒度的并行性。例如,考虑对分区表的查询,该查询只能通过使用不支持并行扫描的索引来有效地实现。规划器可能会选择常规 Index Scan
计划的 Parallel Append
;每个单独的索引扫描都必须由单个进程完整执行,但不同的扫描可以由不同的进程同时执行。
可以使用 enable_parallel_append 来禁用此功能。
如果一个预期会生成并行计划的查询没有生成并行计划,您可以尝试降低 parallel_setup_cost 或 parallel_tuple_cost 的值。当然,这种计划可能最终会比优化器首选的串行计划慢,但这并非总是如此。如果您即使将这些设置设置为非常小的值(例如,将它们都设置为零)也无法获得并行计划,则可能是由于某些原因导致查询优化器无法为您的查询生成并行计划。请参阅第15.2节和第15.4节,了解可能出现这种情况的原因。
在执行并行计划时,您可以使用 EXPLAIN (ANALYZE, VERBOSE)
来显示每个计划节点的每个工作进程的统计信息。这可能有助于确定工作是否在所有计划节点之间均匀分配,并且更普遍地了解计划的性能特征。
如果您在文档中看到任何不正确、与您特定功能的使用体验不符或需要进一步澄清的地方,请使用此表格报告文档问题。