2024 年 9 月 26 日:PostgreSQL 17 发布!
支持版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2

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

FDW 回调函数 GetForeignRelSizeGetForeignPathsGetForeignPlanPlanForeignModifyGetForeignJoinPathsGetForeignUpperPathsPlanDirectModify 必须符合 PostgreSQL 规划器的运作方式。以下是一些关于它们必须做些什么的说明。

rootbaserel 中的信息可用于减少必须从外部表获取的信息量(从而降低成本)。baserel->baserestrictinfo 尤其有趣,因为它包含应用于过滤要获取的行(即 WHERE 子句)的限制条件。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 字段,并且可以生成 fdw_exprsfdw_private 列表以放置在 ForeignScan 计划节点中,这些列表将在执行时可用。这两个列表都必须以 copyObject 知道如何复制的形式表示。 fdw_private 列表没有其他限制,并且不会以任何方式被核心后端解释。 fdw_exprs 列表(如果非 NIL)预计将包含旨在在运行时执行的表达式树。这些树将经过规划器的后处理,以使它们完全可执行。

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

例如,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 表达式树的评估中获取。

从计划节点的限定条件列表中删除的任何子句都必须添加到 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 应以与 GetForeignPaths 处理基本表的方式类似的方式为潜在的远程联接生成 ForeignPath。有关预期联接的信息可以通过上述相同的方式传递到 GetForeignPlan。但是, baserestrictinfo 与联接关系无关;相反,特定联接的相关联接子句将作为单独的参数(extra->restrictlist)传递给 GetForeignJoinPaths

FDW 还可以支持直接执行某些高于扫描和联接级别的计划操作,例如分组或聚合。为了提供此类选项,FDW 应生成路径并将它们插入到适当的 上层关系 中。例如,表示远程聚合的路径应插入 UPPERREL_GROUP_AGG 关系中,使用 add_path。此路径将根据成本与通过读取外国关系的简单扫描路径执行的本地聚合进行比较(请注意,还必须提供此类路径,否则在规划时会出现错误)。如果远程聚合路径获胜(通常是这种情况),它将以通常的方式转换为计划,通过调用 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 中,目标表没有被扫描,因此没有它的 RelOptInfo。由 PlanForeignModify 返回的 ListForeignScan 计划节点的 fdw_private 列表具有相同的限制,即它必须仅包含 copyObject 知道如何复制的结构。

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

提交更正

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