Postgresql 中文操作指南
59.4. Foreign Data Wrapper Query Planning #
FDW 回调函数 GetForeignRelSize、GetForeignPaths、GetForeignPlan、PlanForeignModify、GetForeignJoinPaths、GetForeignUpperPaths 和 PlanDirectModify 必须适合 PostgreSQL 规划器的操作。以下是关于它们必须做什么的一些说明。
root 和 baserel 中的信息可用于减少从外键表中必须获取的信息量(因此降低成本)。baserel→baserestrictinfo 特别有趣,因为它包含应用于过滤要获取的行的数据限制限定词 (WHERE 条款)。(FDW 本身不需要执行这些限定词,因为核心执行器可以检查它们。)baserel→reltarget→exprs 可用于确定要获取哪些列;但请注意,它仅列出 ForeignScan 计划节点必须发出的列,而不是用于限定词评估但查询未输出的列。
FDW 规划函数可以使用各种专用字段来保存信息。通常,你存储在 FDW 专用字段中的任何内容都应分配空间,以便在规划结束时回收它。
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 列表(如果非空)预计包含要在运行时执行的表达式树。这些树将由规划器进行后处理以使其完全可执行。
在 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_s) containing Vars and/or expressions representing the returned columns. This might be used, for example, to show that the FDW has omitted some columns that it noticed won’t be needed for the query. Also, if the FDW can compute expressions used by the query more cheaply than can be done locally, it could add those expressions to _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_s for potential remote joins in much the same way as _GetForeignPaths 适用于基本表。有关预期连接的信息可以通过上述相同的方式传递到 GetForeignPlan。但是,baserestrictinfo 与连接关系无关;相反,特定连接的相关连接子句作为单独的参数 (extra→restrictlist) 传递给 GetForeignJoinPaths。
此外,FDW 可能还支持直接执行某些高于扫描和连接级别的计划操作,例如分组或聚合。为了提供这样的选项,FDW 应该生成路径并将其插入到适当的 upper relation 中。例如,表示远程聚合的路径应插入到 UPPERREL_GROUP_AGG 关系中,使用 add_path。该路径将基于成本与通过读取外部关系的简单扫描路径执行的本地聚合进行比较(请注意,还必须提供这样的路径,否则在计划时会出现错误)。如果远程聚合路径获胜,这通常会发生,它将通过调用 GetForeignPlan 以通常的方式转换为计划。生成此类路径的推荐位置是在 GetForeignUpperPaths 回调函数中,该函数对于每个上层关系(即每个扫描/连接后处理步骤)调用一次,前提是查询的所有基本关系都来自同一 FDW。
PlanForeignModify 和 Section 59.2.4 中描述的其他回调功能的基础假设是:将按常规方式扫描外部关系,然后由本地 ModifyTable 计划节点来驱动各个行更新。对于需要读取本地表和外部表来进行更新的情况,这是必要的常规方法。但是,如果某个操作完全可以由外部服务器执行,那么 FDW 可以生成表示该操作的路径,并将其插入到 UPPERREL_FINAL 上层关系中,这将在 ModifyTable 方法的竞争下进行。此方法还可用来实现远程 SELECT FOR UPDATE,而不是使用 Section 59.2.6 中描述的行锁定回调。请记住,插入到 UPPERREL_FINAL 中的路径负责实现查询的 all 行为。
在计划 UPDATE 或 DELETE 时,PlanForeignModify 和 PlanDirectModify 可以查找外部表 RelOptInfo 结构并利用扫描规划函数先前创建的 baserel→fdw_private 数据。然而,在 INSERT 中,目标表不被扫描,因此没有 RelOptInfo。List 返回的 PlanForeignModify 有与 ForeignScan 计划节点的 fdw_private 列表相同的限制,即它必须仅包含 copyObject 知道如何复制的结构。
具有 INSERT 子句的 ON CONFLICT 不支持指定冲突目标,因为对远程表的唯一约束或排除约束在本地是未知的。这反过来意味着不支持 ON CONFLICT DO UPDATE,因为该规范在那里是强制性的。