如果 FDW 的底层存储机制具有对单独的行加锁以防止这些行被并发 更新的概念,那么 FDW 通常值得以尽可能接近普通 PostgreSQL 表所用语义的方式执行行级 加锁。 这涉及多方面的考虑。
要做的一个关键决定是执行 早期加锁还是 延迟加锁。 在早期加锁中,行在第一次从底层存储检索时就被加锁; 而在延迟加锁中,只有当已知该行需要被加锁时才加锁。 (这种区别的产生是因为某些行可能会被本地检查的限制或连接 条件丢弃。) 早期加锁简单得多,并避免了到远程存储的额外往返,但它可能导致 锁定了本不必锁定的行,造成并发性下降甚至意外的 死锁。 此外,只有当被加锁的行以后能够被唯一地重新标识时,延迟加锁才 可行。 最好行标识符应当像 PostgreSQL 的 TID 那样,标识该行的 一个特定版本。
默认情况下,PostgreSQL 在对接 FDW 时 不考虑加锁问题,但 FDW 无需核心代码的任何显式支持就能执行 早期加锁。 Section 58.2.6 中描述的 API 函数 (在 PostgreSQL 9.5 中加入)允许 FDW 在 愿意时使用延迟加锁。
另一个考虑是:在
READ COMMITTED
隔离模式下,
PostgreSQL 可能需要针对某个目标元组
的更新版本重新检查限制和连接条件。
重新检查连接条件要求重新获得之前与目标元组连接过的非目标行的
副本。
在处理标准
PostgreSQL 表时,这通过把非目标表的
TID 包含在通过连接投影的列列表中、然后在需要时重新取回非目标
行来完成。
这种方法保持连接数据集紧凑,但它要求低成本的重新取回能力,
以及一个能够唯一标识要重新取回的行版本的 TID。
因此,默认情况下对外部表采用的方法是把从外部表取回的整行的
一份副本包含在通过连接投影的列列表中。
这不会对 FDW 提出特殊要求,但可能导致归并连接和哈希连接的性能
下降。
能够满足重新取回要求的 FDW 可以选择采用第一种方法。
对外部表的
UPDATE 或
DELETE,建议对该目标表执行的
ForeignScan 操作对它取回的行执行早期加锁,
或许可以通过
SELECT FOR UPDATE 的等价物实现。
FDW 可以在计划期通过把表的 relid 与
root->parse->resultRelation 比较,
或在执行期使用
ExecRelationIsTargetRelation(),来检测一个
表是否是
UPDATE/DELETE 的目标。
另一种可能是在
ExecForeignUpdate 或
ExecForeignDelete
回调中执行延迟加锁,但没有为这一点提供特殊支持。
对被
SELECT
FOR UPDATE/SHARE 命令指定要加锁的外部表,
ForeignScan 操作同样可以通过用
SELECT FOR UPDATE/SHARE 的等价物取回元组来
执行早期加锁。
要改为执行延迟加锁,请提供在
Section 58.2.6 中定义的回调函数。
在
GetForeignRowMarkType 中,根据请求的锁
强度选择 rowmark 选项
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。
这会导致以该值作为
markType 调用
RefetchForeignRow;它随后应当在不获取任何
新锁的情况下重新取回该行。
(如果您有
GetForeignRowMarkType 函数但不想重新取回
未加锁的行,请为
LCS_NONE 选择选项
ROW_MARK_COPY。)
更多信息见
src/include/nodes/lockoptions.h、
src/include/nodes/plannodes.h 中
RowMarkType 和
PlanRowMark 的注释,以及
src/include/nodes/execnodes.h 中
ExecRowMark 的注释。