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