如果 FDW 的底层存储机制具有锁定各个行以防止这些行同时更新的概念,那么 FDW 通常值得使用尽可能接近普通 PostgreSQL 表中使用的语义来执行行级锁定。这涉及多个考虑因素。
要作出的一个关键决策是执行早期锁定还是延迟锁定。在早期锁定中,当首次从底层存储中检索行时会锁定该行,而在延迟锁定中,只有当已知需要锁定该行时才会锁定该行。(差异产生于某些行可能会因本地检查的限制或连接条件而被丢弃。)早期锁定简单得多,并且避免了与远程存储的额外往返,但它可能会导致锁定不需要锁定的行,从而导致并发性降低甚至意外死锁。此外,只有当要锁定的行稍后可以被唯一重新识别时,才有可能进行延迟锁定。最好行标识符应该标识行的特定版本,就像 PostgreSQL TID 所做的那样。
默认情况下,PostgreSQL 在与 FDW 交互时会忽略锁定注意事项,但 FDW 可以执行早期锁定,而无需核心代码的任何显式支持。在 第 59.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
进行比较在计划时检测表是否是 UPDATE
/DELETE
目标,或在执行时通过使用 ExecRelationIsTargetRelation()
进行检测。另一种可能性是在 ExecForeignUpdate
或 ExecForeignDelete
回调中执行延迟锁定,但没有对此提供特殊支持。
对于指定由 SELECT FOR UPDATE/SHARE
命令锁定的外部表,ForeignScan
操作可以通过使用等效于 SELECT FOR UPDATE/SHARE
的元组来再次执行早期锁定。要执行延迟锁定,请提供在 第 59.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
的注释以获取更多信息。