59.4. 外部数据包装器查询计划#
FDW 回调函数GetForeignRelSize
、GetForeignPaths
、GetForeignPlan
、PlanForeignModify
、GetForeignJoinPaths
、GetForeignUpperPaths
和PlanDirectModify
必须符合PostgreSQL规划器的运行方式。以下是它们必须执行的操作的一些说明。
可以使用root
和baserel
中的信息来减少必须从外部表中获取的信息量(从而降低成本)。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
字段,并可以生成fdw_exprs
和fdw_private
列表,以便放置在ForeignScan
计划节点中,它们将在执行时可用。这两个列表都必须以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
隔离级别下行为正确。当查询中涉及的其他某些表发生并发更新时,执行器可能需要验证元组仍然满足所有原始 qual,可能针对一组不同的参数值。使用fdw_recheck_quals
通常比在RecheckForeignScan
中实现检查更容易,但当外连接已被下推时,此方法将不足以使用,因为在这种情况下,连接元组可能会使某些字段变为 NULL,而不会完全拒绝元组。
FDW 可以填充的另一个ForeignScan
字段是fdw_scan_tlist
,它描述了 FDW 为此计划节点返回的元组。对于简单的外键表扫描,可以将其设置为NIL
,这意味着返回的元组具有为外键表声明的行类型。非NIL
值必须是目标列表(TargetEntry
列表),其中包含表示返回列的变量和/或表达式。例如,这可以用来表明 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
和第 59.2.4 节中描述的其他回调基于以下假设而设计:外键关系将以通常的方式扫描,然后单个行更新将由本地ModifyTable
计划节点驱动。对于需要读取本地表和外键表的更新,此方法是必要的。但是,如果操作可以完全由外键服务器执行,则 FDW 可以生成表示该操作的路径并将其插入到UPPERREL_FINAL
上层关系中,它将在那里与ModifyTable
方法竞争。此方法还可用于实现远程SELECT FOR UPDATE
,而不是使用第 59.2.6 节中描述的行锁定回调。请记住,插入到UPPERREL_FINAL
中的路径负责实现查询的所有行为。
在计划UPDATE
或DELETE
时,PlanForeignModify
和PlanDirectModify
可以查找外键表的RelOptInfo
结构并利用扫描规划函数之前创建的baserel->fdw_private
数据。但是,在INSERT
中,目标表不会被扫描,因此没有RelOptInfo
。由PlanForeignModify
返回的List
具有与ForeignScan
计划节点的fdw_private
列表相同的限制,即它必须仅包含copyObject
知道如何复制的结构。
INSERT
带有ON CONFLICT
子句不支持指定冲突目标,因为远程表上的唯一约束或排除约束在本地是未知的。这反过来意味着ON CONFLICT DO UPDATE
不受支持,因为该规范在那里是强制性的。