如果 FDW 的底层存储机制具有锁定单个行以防止这些行的并发更新的概念,那么 FDW 通常值得执行行级锁定,尽可能逼近普通 PostgreSQL 表中使用的语义。这涉及多个注意事项。
要做的一个关键决定是执行早期锁定还是晚期锁定。在早期锁定中,当行从基础存储中首次检索时,将锁定该行,而在晚期锁定中,只有在知道需要锁定该行时才锁定该行。(差异产生的原因是某些行可能会由于本地检查的限制或联接条件而被废弃。)早期锁定更加简单,避免了到远程存储的额外往返,但它可能会导致对无需锁定的行的锁定,从而导致并发性降低或甚至意外的死锁。此外,只有当可以随后重新唯一识别待锁定的行时才有可能进行晚期锁定。理想情况下,行标识符应标识行的特定版本,就像PostgreSQL TID 所做的那样。
在与 FDW 交互时,PostgreSQL 默认情况下会忽略锁定注意事项,但 FDW 可以执行早期锁定,而无需核心代码的任何显式支持。在57.2.6 节中描述的 API 函数(在PostgreSQL 9.5 中添加)允许 FDW 在需要时使用晚期锁定。
另一个需要考虑的方面是在READ COMMITTED
隔离模式中,PostgreSQL可能需要根据某些目标元组的更新版本重新检查约束和联接条件。重新检查联接条件需要重新获取以前与目标元组联接的非目标行的副本。在使用标准PostgreSQL表时,可以通过将非目标表的 TID 包含在通过联接投影的列列表中来完成此操作,然后在需要时重新获取非目标行。此方法保持了联接数据集的紧凑性,但它需要低成本的重新获取功能,以及能够唯一标识要重新获取的行版本的 TID。因此,在默认情况下,与外部表一起使用的方法是将从外部表获取的整个行的副本包括在通过联接投影的列列表中。这不会对 FDW 提出特殊要求,但可能会导致合并和散列联接的性能降低。能够满足重新获取要求的 FDW 可以选择以第一种方式进行操作。
对于外键表上的 UPDATE
或 DELETE
,建议在目标表上进行 ForeignScan
操作,以提前锁定它获取的行,可能通过 SELECT FOR UPDATE
等效项。FDW 可以在计划时间通过将其 relid 与 root->parse->resultRelation
进行比较,或在执行时间通过使用 ExecRelationIsTargetRelation()
来检测一个表是不是 UPDATE
/DELETE
目标。另一种可能性是在 ExecForeignUpdate
或 ExecForeignDelete
回调中进行延迟锁定,但对此没有提供特定的支持。
对于被指定由 SELECT FOR UPDATE/SHARE
命令锁定的外键表,ForeignScan
操作可以通过用 SELECT FOR UPDATE/SHARE
等效项获取元组来再次进行早期锁定。要改用延迟锁定,请提供 第 57.2.6 节 中定义的回调函数。在 GetForeignRowMarkType
中,根据请求的锁定强度,选择行标记选项 ROW_MARK_EXCLUSIVE
、ROW_MARK_NOKEYEXCLUSIVE
、ROW_MARK_SHARE
或 ROW_MARK_KEYSHARE
。(无论选择哪四个选项之一,核心代码的表现都会一样。)在其他地方,可以在计划时间使用 get_plan_rowmark
,或在执行时间使用 ExecFindRowMark
来检测一个外键表是否被指定要由该类型的命令锁定;必须检查是否返回了一个非空行标记结构,且它的 strength
字段不是 LCS_NONE
。
最后,对于在一个 UPDATE
、DELETE
或 SELECT FOR UPDATE/SHARE
中使用的外部表,如果不指定将其行锁定,您可以通过 GetForeignRowMarkType
在看到锁定强度 LCS_NONE
时选择选项 ROW_MARK_REFERENCE
,来覆盖默认的复制整行选项。这将 RefetchForeignRow
被调用,其中 markType
的值为以上值;然后它应该重新获取行,而无需获取任何新的锁。(如果您有 GetForeignRowMarkType
函数,但不希望重新获取解锁行,则为 LCS_NONE
选择选项 ROW_MARK_COPY
。)
有关其他信息,请参见 src/include/nodes/lockoptions.h
,src/include/nodes/plannodes.h
中 RowMarkType
和 PlanRowMark
的注释,以及 src/include/nodes/execnodes.h
中 ExecRowMark
的注释。