59.5. 外键数据包装器中的行锁定#
如果 FDW 的底层存储机制具有锁定各个行以防止这些行同时更新的概念,那么 FDW 通常值得使用尽可能接近普通PostgreSQL表中使用的语义来执行行级锁定。这涉及多个考虑因素。
要作出的一个关键决策是执行早期锁定还是延迟锁定。在早期锁定中,当首次从底层存储中检索行时会锁定该行,而在延迟锁定中,只有当已知需要锁定该行时才会锁定该行。(差异产生于某些行可能会因本地检查的限制或连接条件而被丢弃。)早期锁定简单得多,并且避免了与远程存储的额外往返,但它可能会导致锁定不需要锁定的行,从而导致并发性降低甚至意外死锁。此外,只有当要锁定的行稍后可以被唯一重新识别时,才有可能进行延迟锁定。最好行标识符应该标识行的特定版本,就像PostgreSQLTID 所做的那样。
默认情况下,PostgreSQL在与 FDW 交互时会忽略锁定注意事项,但 FDW 可以执行早期锁定,而无需核心代码的任何显式支持。在第 59.2.6 节中描述的 API 函数(PostgreSQL9.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
的注释以获取更多信息。