如果 FDW 的底层存储机制具有锁定单个行的概念,以防止对这些行的并发更新,则 FDW 通常值得执行行级锁定,并尽可能地接近普通 PostgreSQL 表中使用的语义。这涉及多个因素。
需要做出的一项关键决定是执行早期锁定还是后期锁定。在早期锁定中,在首次从底层存储中检索行时锁定该行,而在后期锁定中,仅在已知需要锁定该行时才锁定该行。(区别在于,某些行可能会被本地检查的限制或连接条件丢弃。)早期锁定更简单,避免了额外的往返远程存储的行程,但它会导致对不需要锁定的行的锁定,从而导致并发性降低,甚至出现意外死锁。此外,只有当要锁定的行以后可以被唯一地重新识别时,后期锁定才可能实现。最好是行标识符应该识别行的特定版本,就像 PostgreSQL TIDs 一样。
默认情况下,PostgreSQL 在与 FDW 交互时会忽略锁定考虑因素,但 FDW 可以执行早期锁定,而无需核心代码的任何显式支持。在 PostgreSQL 9.5 中添加的 第 57.2.6 节 中描述的 API 函数允许 FDW 使用后期锁定,如果需要的话。
另一个考虑因素是,在 READ COMMITTED
隔离级别下,PostgreSQL 可能需要针对某些目标元组的更新版本重新检查限制和连接条件。重新检查连接条件需要重新获取以前与目标元组连接的非目标行的副本。在处理标准 PostgreSQL 表时,这是通过将非目标表的 TIDs 包含在通过连接投影的列列表中来完成的,然后在需要时重新获取非目标行。这种方法使连接数据集保持紧凑,但它需要廉价的重新获取功能,以及可以唯一标识要重新获取的行版本的 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
的方式获取元组来再次执行早期锁定。要执行后期锁定,请提供 第 57.2.6 节 中定义的回调函数。在 GetForeignRowMarkType
中,根据请求的锁定强度选择行标记选项 ROW_MARK_EXCLUSIVE
、ROW_MARK_NOKEYEXCLUSIVE
、ROW_MARK_SHARE
或 ROW_MARK_KEYSHARE
。(无论您选择哪一种这四种选项,核心代码的行为都将相同。)在其他地方,您可以通过在计划时间使用 get_plan_rowmark
,或者在执行时间使用 ExecFindRowMark
来检测是否指定了对外部表的这种类型的命令进行锁定;您不仅要检查是否返回了非空的 rowmark 结构,还要检查它的 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
的注释。
如果您在文档中看到任何不正确的内容,不符合您对特定功能的体验,或者需要进一步澄清,请使用 此表单 报告文档问题。