如果 FDW 的底层存储机制具有锁定单个行以防止并发更新的机制,那么对于 FDW 来说,进行行级锁定通常是值得的,并且应尽可能接近普通 PostgreSQL 表的语义。这涉及到多个考虑因素。
需要做出的一个关键决定是执行“早期锁定”还是“晚期锁定”。在早期锁定中,当行第一次从底层存储检索时就会被锁定,而在晚期锁定中,只有当已知需要锁定该行时才锁定它。(区别在于一些行可能会被本地检查的限制或连接条件丢弃。)早期锁定更简单,并且可以避免额外的与远程存储的往返通信,但它可能导致锁定不需要锁定的行,从而降低并发性甚至导致意外的死锁。此外,晚期锁定仅在可以稍后唯一地重新识别要锁定的行时才可能。最好是行标识符能识别行的特定版本,就像 PostgreSQL 的 TID 那样。
默认情况下,PostgreSQL 在与 FDW 交互时会忽略锁定方面的考虑,但 FDW 可以在没有任何核心代码显式支持的情况下执行早期锁定。在 PostgreSQL 9.5 中添加的 第 58.2.6 节 中描述的 API 函数允许 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
的元组来执行早期锁定。要改为执行晚期锁定,请提供 第 58.2.6 节 中定义的 callback 函数。在 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
的注释。
如果您在文档中发现任何不正确之处、与您对特定功能的实际体验不符或需要进一步澄清的地方,请使用 此表格 来报告文档问题。