Postgresql 中文操作指南

59.5. Row Locking in Foreign Data Wrappers #

如果 FDW 的底层存储机制具有锁定个别行以防止对这些行进行并发更新的概念,那么通常值得 FDW 对行级锁定进行尽可能接近普通 PostgreSQL 表中使用的语义的近似处理。其中涉及多个考虑因素。

需要做出的一个关键决策是执行 early locking 还是 late locking。在早期锁定中,当行首次从底层存储中检索到时,该行将被锁定,而在后期锁定中,仅当已知需要锁定该行时才锁定该行。(出现差异的原因是某些行可能会被本地检查的限制或联接条件丢弃。)早期锁定要简单得多,并且避免了对远程存储的额外往返,但它可能会导致锁定不必锁定的行,从而导致并发性降低,甚至出现意外的死锁。此外,只有当待锁定的行以后可以被唯一重新识别时,才可能进行后期锁定。最好是行标识符应该标识行的特定版本,就像 PostgreSQL TID 所做的那样。

默认情况下,PostgreSQL在与FDW交互时忽略锁定注意事项,但FDW可以在没有任何来自核心代码的显式支持的情况下执行早期锁定。 Section 59.2.6中描述的API函数(在PostgreSQL 9.5中添加)允许FDW在希望时使用延迟锁定。

另一个考虑因素是,在 READ COMMITTED 隔离模式中,PostgreSQL 可能需要针对某些目标元组的更新版本重新检查限制和连接条件。重新检查联接条件需要重新获取以前与目标元组联接的非目标行的副本。在使用标准 PostgreSQL 表时,这是通过将非目标表的 TID 包含在通过联接投影的列列表中来完成的,然后在需要时重新获取非目标行。此方法使联接数据集保持紧凑,但它需要廉价的重新获取功能以及能够唯一标识要重新获取的行版本的 TID。因此,默认情况下,与外部表配合使用的方法是将从外部表获取的整行副本包含在通过联接投影的列列表中。这不会对 FDW 提出任何特殊要求,但可能会导致合并和哈希联接的性能降低。能够满足重新获取要求的 FDW 可以选择以第一种方式进行。

对于外部表上的 UPDATEDELETE,建议目标表上的 ForeignScan 操作对其获取的行执行早期锁定,可能通过相当于 SELECT FOR UPDATE 的方式。FDW 可以通过将表 relid 与 root→parse→resultRelation_进行比较来在计划时检测表是否是 _UPDATE/DELETE 目标,或在执行时使用 ExecRelationIsTargetRelation() 进行检测。另一种可能性是在 ExecForeignUpdateExecForeignDelete 回调中执行后期锁定,但没有为此提供特殊支持。

对于指定由_SELECT FOR UPDATE/SHARE_命令锁定的外部表,ForeignScan_操作可以通过使用等效于_SELECT FOR UPDATE/SHARE_的元组再次执行早期锁定。要改为执行延迟锁定,请提供 Section 59.2.6中定义的回调函数。在_GetForeignRowMarkType_中,根据请求的锁定强度,选择行标记选项_ROW_MARK_EXCLUSIVEROW_MARK_NOKEYEXCLUSIVEROW_MARK_SHARE_或_ROW_MARK_KEYSHARE。(无论选择哪四个选项之一,核心代码的行为都相同。)在其他地方,你可以使用_get_plan_rowmark_在规划时或_ExecFindRowMark_在执行时检测外部表是否已指定由这种类型的命令锁定;必须检查不只是返回非空行标记结构,并且其_strength_字段不是_LCS_NONE_。

最后,对于用于 UPDATEDELETESELECT FOR UPDATE/SHARE 命令但未指定为行锁定的外部表,您可以通过让 GetForeignRowMarkType 在看到锁定强度为 LCS_NONE 时选择选项 ROW_MARK_REFERENCE 来覆盖复制整行的默认选择。这将导致针对 markType 调用 RefetchForeignRow 时具有该值;它应该在不获取任何新锁的情况下重新获取行。(如果您有 GetForeignRowMarkType 函数但不希望重新获取解锁的行,则为 LCS_NONE 选择选项 ROW_MARK_COPY。)

有关更多信息,请参见 src/include/nodes/lockoptions.hsrc/include/nodes/plannodes.hRowMarkTypePlanRowMark 的注释以及 src/include/nodes/execnodes.hExecRowMark 的注释。