因为每个工作者只执行完成计划的并行部分,所以不可能简单地产生一个普通查询计划并使用多个工作者运行它。每个工作者都会产生输出结果集的一个完全拷贝,因而查询并不会比普通查询运行得更快甚至还会产生不正确的结果。相反,计划的并行部分一定被查询优化器在内部当作一个部分计划,即它必须被构建出来,这样每一个执行该计划的进程将以无重复的方式产生输出行的一个子集,即保证每一个所需要的输出行正好只被一个合作进程生成。通常,这意味着该查询的驱动表上的扫描必须是一种可并行的扫描。
目前支持以下类型的并行感知表扫描。
在并行顺序扫描中,表的块将被分成范围并在合作进程之间共享。 每个工作进程将在请求额外的块范围之前完成其给定范围的扫描。
在并行位图堆扫描中,选择一个进程作为领导者。 该进程执行一个或多个索引的扫描,并构建一个位图,指示需要访问哪些表块。 然后将这些块分配给合作进程,就像在并行顺序扫描中一样。 换句话说,堆扫描是并行执行的,但底层索引扫描不是。
在并行索引扫描或并行索引唯一扫描中, 合作进程轮流从索引中读取数据。 目前,仅支持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)来显示每个计划节点的每个工作者的统计信息。
这可能有助于确定工作是否在所有计划节点之间均匀分配,以及
更一般地理解计划的性能特征。