支持的版本:当前 (17) / 16 / 15 / 14 / 13
开发版本:devel
不支持的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2

57.4. 外部数据包装器查询规划 #

FDW 回调函数 GetForeignRelSizeGetForeignPathsGetForeignPlanPlanForeignModifyGetForeignJoinPathsGetForeignUpperPathsPlanDirectModify 必须适应 PostgreSQL 规划器的工作方式。以下是关于它们必须执行的操作的一些说明。

可以使用 rootbaserel 中的信息来减少必须从外部表中获取的信息量(从而降低成本)。baserel->baserestrictinfo 特别有趣,因为它包含应该用于过滤要获取的行的限制限定词(WHERE 子句)。(FDW 本身不需要强制执行这些限定词,因为核心执行器可以改为检查它们。)baserel->reltarget->exprs 可用于确定需要获取哪些列;但请注意,它仅列出必须由 ForeignScan 计划节点发出的列,而不是在限定词评估中使用但查询未输出的列。

各种私有字段可供 FDW 规划函数用来保留信息。一般来说,您存储在 FDW 私有字段中的任何内容都应该使用 palloc 分配,以便在规划结束时回收。

baserel->fdw_private 是一个 void 指针,可供 FDW 规划函数存储与特定外部表相关的信息。核心规划器不会触及它,除非在创建 RelOptInfo 节点时将其初始化为 NULL。它可用于将信息从 GetForeignRelSize 传递到 GetForeignPaths 和/或从 GetForeignPaths 传递到 GetForeignPlan,从而避免重新计算。

GetForeignPaths 可以通过将私有信息存储在 ForeignPath 节点的 fdw_private 字段中来识别不同访问路径的含义。fdw_private 被声明为 List 指针,但实际上可以包含任何内容,因为核心规划器不会触及它。但是,最佳实践是使用可由 nodeToString 转储的表示形式,以便与后端中提供的调试支持一起使用。

GetForeignPlan 可以检查所选 ForeignPath 节点的 fdw_private 字段,并且可以生成要放置在 ForeignScan 计划节点中的 fdw_exprsfdw_private 列表,它们将在执行时可用。这两个列表都必须以 copyObject 知道如何复制的形式表示。fdw_private 列表没有其他限制,并且核心后端不会以任何方式解释它。fdw_exprs 列表(如果不是 NIL)应包含旨在在运行时执行的表达式树。这些树将由规划器进行后处理,以使其完全可执行。

GetForeignPlan 中,通常可以将传入的目标列表按原样复制到计划节点中。传入的 scan_clauses 列表包含与 baserel->baserestrictinfo 相同的子句,但可能会重新排序以获得更好的执行效率。在简单的情况下,FDW 可以只从 scan_clauses 列表中剥离 RestrictInfo 节点(使用 extract_actual_clauses)并将所有子句放入计划节点的 qual 列表中,这意味着所有子句都将在运行时由执行器检查。更复杂的 FDW 可能会在内部检查某些子句,在这种情况下,可以从计划节点的 qual 列表中删除这些子句,以便执行器不会浪费时间重新检查它们。

例如,FDW 可能会识别出一些形式为 foreign_variable = sub_expression 的限制子句,它确定在给定本地评估的 sub_expression 值的情况下可以在远程服务器上执行这些子句。此类子句的实际识别应在 GetForeignPaths 期间进行,因为它会影响路径的成本估算。路径的 fdw_private 字段可能包含指向已识别的子句的 RestrictInfo 节点的指针。然后 GetForeignPlan 将从 scan_clauses 中删除该子句,但将 sub_expression 添加到 fdw_exprs 以确保将其转换为可执行的形式。它可能还会将控制信息放入计划节点的 fdw_private 字段中,以告诉执行函数在运行时执行什么操作。传输到远程服务器的查询将涉及类似于 WHERE foreign_variable = $1 的内容,参数值在运行时从 fdw_exprs 表达式树的评估中获得。

从计划节点的 qual 列表中删除的任何子句都必须改为添加到 fdw_recheck_quals 或由 RecheckForeignScan 重新检查,以确保在 READ COMMITTED 隔离级别下的正确行为。当发生对查询中涉及的某些其他表的并发更新时,执行器可能需要验证所有原始限定词对于该元组是否仍然满足,可能是针对一组不同的参数值。使用 fdw_recheck_quals 通常比在 RecheckForeignScan 内部实现检查更容易,但当外部连接已被下推时,此方法将不足够,因为在这种情况下,连接元组可能有一些字段变为 NULL 而不会完全拒绝该元组。

FDW 可以填充的另一个 ForeignScan 字段是 fdw_scan_tlist,它描述了 FDW 为此计划节点返回的元组。对于简单的外部表扫描,这可以设置为 NIL,这意味着返回的元组具有为外部表声明的行类型。非 NIL 值必须是目标列表(TargetEntry 的列表),其中包含表示返回列的 Vars 和/或表达式。例如,这可能用于表明 FDW 已省略了它注意到查询不需要的一些列。此外,如果 FDW 可以比本地更便宜地计算查询使用的表达式,它可以将这些表达式添加到 fdw_scan_tlist。请注意,连接计划(从 GetForeignJoinPaths 创建的路径创建)必须始终提供 fdw_scan_tlist 来描述它们将返回的列集。

FDW 应始终构造至少一个仅依赖于表的限制子句的路径。在连接查询中,它也可能选择构造依赖于连接子句的路径,例如 foreign_variable = local_variable。在 baserel->baserestrictinfo 中找不到此类子句,但必须在关系的连接列表中查找。使用此类子句的路径称为参数化路径。它必须使用 param_info 的适当值标识所选连接子句中使用的其他关系;使用 get_baserel_parampathinfo 计算该值。在 GetForeignPlan 中,连接子句的 local_variable 部分将被添加到 fdw_exprs,然后在运行时,该情况的工作方式与普通限制子句相同。

如果 FDW 支持远程连接,GetForeignJoinPaths 应该为潜在的远程连接生成 ForeignPath,其方式与 GetForeignPaths 用于基表的方式大致相同。有关预期连接的信息可以通过上述相同的方式传递到 GetForeignPlan。但是,baserestrictinfo 与连接关系无关;相反,特定连接的相关连接子句作为单独的参数 (extra->restrictlist) 传递给 GetForeignJoinPaths

此外,FDW 可能还支持直接执行一些高于扫描和连接级别的计划操作,例如分组或聚合。为了提供此类选项,FDW 应该生成路径并将它们插入到适当的上层关系中。例如,一个表示远程聚合的路径应该使用 add_path 插入到 UPPERREL_GROUP_AGG 关系中。这个路径将与通过读取外表简单扫描路径执行的本地聚合(请注意,还必须提供这样的路径,否则在计划时会出现错误)在成本基础上进行比较。如果远程聚合路径胜出(通常会这样),它将以通常的方式通过调用 GetForeignPlan 转换为计划。生成此类路径的推荐位置是在 GetForeignUpperPaths 回调函数中,该函数为每个上层关系(即每个扫描/连接后的处理步骤)调用,前提是查询的所有基本关系都来自同一个 FDW。

PlanForeignModify第 57.2.4 节中描述的其他回调函数是围绕这样的假设设计的:外表将以通常的方式进行扫描,然后由本地 ModifyTable 计划节点驱动执行各个行更新。对于更新需要读取本地表以及外表的一般情况,这种方法是必要的。但是,如果该操作可以完全由外部服务器执行,则 FDW 可以生成一个表示该操作的路径并将其插入到 UPPERREL_FINAL 上层关系中,在那里它将与 ModifyTable 方法竞争。这种方法也可以用来实现远程的 SELECT FOR UPDATE,而不是使用 第 57.2.6 节中描述的行锁定回调函数。请记住,插入到 UPPERREL_FINAL 中的路径负责实现查询的所有行为。

在计划 UPDATEDELETE 时,PlanForeignModifyPlanDirectModify 可以查找外表的 RelOptInfo 结构,并使用先前由扫描计划函数创建的 baserel->fdw_private 数据。但是,在 INSERT 中,目标表不会被扫描,因此它没有 RelOptInfoPlanForeignModify 返回的 List 具有与 ForeignScan 计划节点的 fdw_private 列表相同的限制,也就是说它必须仅包含 copyObject 知道如何复制的结构。

带有 ON CONFLICT 子句的 INSERT 不支持指定冲突目标,因为远程表上的唯一约束或排除约束在本地是未知的。反过来,这意味着不支持 ON CONFLICT DO UPDATE,因为此处规范是强制性的。

提交更正

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