15.3. Parallel 计划

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

15 .3.1. Parallel 扫描

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

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

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

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

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

15 .3.2. 并行联接

就像在非并行计划中一样,可以使用嵌套循环,哈希连接或合并连接将驱动表连接到一个或多个其他表。联接的内侧可以是计划者以其他方式支持的任何非并行计划,只要可以在并行工作程序中安全运行即可。例如,如果选择了嵌套循环联接,则内部计划可以是索引扫描,该索引查找从联接的外侧获取的值。

每个工作人员将完全执行联接的内侧。对于嵌套循环,这通常不是问题,但是对于涉及哈希或合并联接的情况,效率可能较低。例如,对于散列连接,此限制意味着在每个工作进程中都构建了一个相同的散列表,这对于与小表的连接有效,但在内部表较大时可能无效。对于合并联接,这可能意味着每个工作人员执行单独的内部关系排序,这可能很慢。当然,在这种并行计划效率低下的情况下,查询计划者通常会选择其他计划(可能是不使用并行性的计划)。

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 计划提示

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

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