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 可以选择第一种方法。

对于外部表上的UPDATEDELETE,建议目标表上的ForeignScan操作对其获取的行执行早期锁定,也许通过等效于SELECT FOR UPDATE进行。 FDW 可以在计划时通过将表的相对位置与root->parse->resultRelation进行比较,或者在执行时使用ExecRelationIsTargetRelation()来检测表是UPDATE/DELETE目标。另一种可能性是在ExecForeignUpdateExecForeignDelete回调中执行后期锁定,但是对此不提供任何特殊支持。

对于指定由SELECT FOR UPDATE/SHARE命令锁定的外部表,ForeignScan操作可以通过获取等效于SELECT FOR UPDATE/SHARE的 Tuples 来再次执行早期锁定。要执行后期锁定,请提供Section 57.2.5中定义的回调函数。在GetForeignRowMarkType中,根据请求的锁定强度选择行标记选项ROW_MARK_EXCLUSIVEROW_MARK_NOKEYEXCLUSIVEROW_MARK_SHAREROW_MARK_KEYSHARE。 (无论您选择这四个选项中的哪一个,核心代码都将发挥相同的作用.)在其他地方,您可以通过在计划时使用get_plan_rowmark或在执行时使用ExecFindRowMark来检测是否已将外部表指定为通过这种类型的命令锁定;您不仅必须检查是否返回了非 null 的行标记结构,还必须检查其strength字段不是LCS_NONE

最后,对于在UPDATEDELETESELECT FOR UPDATE/SHARE命令中使用但未指定为行锁定的外部表,您可以通过使用GetForeignRowMarkType选择选项ROW_MARK_REFERENCE(当看到锁定强度LCS_NONE时)来覆盖默认选项以复制整个行。这将导致使用markType的值调用RefetchForeignRow;然后应该重新获取该行而不获取任何新锁。 (如果您具有GetForeignRowMarkType函数,但不想重新获取未锁定的行,请为LCS_NONE选择选项ROW_MARK_COPY.)

有关其他信息,请参见src/include/nodes/lockoptions.hsrc/include/nodes/plannodes.h中的RowMarkTypePlanRowMark的 Comments 以及src/include/nodes/execnodes.h中的ExecRowMark的 Comments。