15.3. Parallel 计划

因为每个工作人员都执行计划的并行部分直到完成,所以不可能简单地采用一个普通的查询计划并使用多个工作人员来运行它。每个工作程序都会生成输出结果集的完整副本,因此查询的运行速度不会比正常情况下快,但是会产生错误的结果。相反,计划的并行部分必须是查询优化器内部称为“部分计划”的部分;也就是说,必须将其构造为使得执行计划的每个流程将仅生成输出行的子集,以确保每个所需的输出行都恰好由一个协作流程生成。通常,这意味着对查询的驱动表的扫描必须是并行感知的扫描。

15 .3.1. Parallel 扫描

当前支持以下类型的并行感知表扫描。

  • 在“并行 Sequences 扫描”中,表的块将在协作过程之间分配。一次分发一个块,以便对表的访问保持 Sequences。

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

  • 在“并行索引扫描”或“仅并行索引扫描”中,协作过程轮流从索引中读取数据。当前,仅 btree 索引支持并行索引扫描。每个进程将声明一个索引块,并将扫描并返回该块引用的所有 Tuples;其他进程可以同时从不同的索引块返回 Tuples。并行 btree 扫描的结果在每个工作进程中按排序 Sequences 返回。

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

15 .3.2. 并行联接

就像在非并行计划中一样,可以使用嵌套循环,哈希连接或合并连接将驱动表连接到一个或多个其他表。联接的内侧可以是计划者以其他方式支持的任何非并行计划,只要可以在并行工作程序中安全运行即可。根据连接类型,内侧也可以是 Parallel 平面。

  • 嵌套循环连接中,内侧始终不 Parallel。尽管它是完全执行的,但是如果内侧是索引扫描,则这样做是有效的,因为外部 Tuples 以及在索引中查找值的循环在合作过程中被划分。

  • 在* merge join *中,内侧始终是非并行计划,因此完全执行。这可能效率很低,尤其是在必须执行某种排序的情况下,因为工作和结果数据在每个合作过程中都是重复的。

  • 在* hash join (不带“ parallel”前缀)中,每个协作过程都会完全执行内侧,以构建哈希表的相同副本。如果哈希表很大或计划很昂贵,这可能是无效的。在 parallel hash join 中,内侧是 parallel hash *,它在合作进程之间划分了构建共享哈希表的工作。

15 .3.3. 并行聚合

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

因为Finalize Aggregate节点是在领导者进程上运行的,所以与 Importing 行数相比,产生相对较大数量的组的查询对查询计划者而言不太有利。例如,在最坏的情况下,Finalize Aggregate节点看到的组数可能与Partial Aggregate阶段中所有工作进程看到的 Importing 行数一样多。对于这种情况,使用并行聚合显然不会对性能产生任何好处。查询计划者在计划过程中会考虑到这一点,并且在这种情况下不太可能选择并行聚合。

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

15 .3.4. Parallel 追加

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

当在并行计划中使用Append节点时,每个进程将按照它们出现的 Sequences 执行子计划,以便所有参与的进程合作执行第一个子计划,直到完成为止,然后移至第二个计划。大约在同一时间。相反,当使用Parallel Append时,执行程序将在其子计划中尽可能平均地分散参与的进程,以便同时执行多个子计划。这样可以避免争用,也可以避免在从未执行子计划的进程中支付子计划的启动成本。

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

enable_parallel_append可用于禁用此功能。

15 .3.5. Parallel 计划提示

如果预期这样做不会产生并行计划,则可以尝试减少parallel_setup_costparallel_tuple_cost。当然,该计划可能会比计划者偏爱的串行计划要慢,但并非总是如此。如果即使这些设置的值很小,也没有得到并行计划(例如,将它们都设置为零后),则可能由于某些原因查询计划程序无法为您的查询生成并行计划。有关为何可能出现这种情况的信息,请参见Section 15.2Section 15.4

执行并行计划时,可以使用EXPLAIN (ANALYZE, VERBOSE)显示每个计划节点的按工作人员统计。这对于确定工作是否在所有计划节点之间平均分配以及更普遍地理解计划的性能 Feature 很有用。