如果某个 FDW 的底层存储机制具有锁定单行以防止并发更新这些行的概念,那么通常值得让该 FDW 执行行级锁定,以尽可能接近普通 PostgreSQL 表所采用的语义。这里面需要考虑多个方面。
一个关键决策是执行早期锁定还是晚期锁定。在早期锁定中,一行在第一次从底层存储中取回时就会被锁定;而在晚期锁定中,只有在确认它确实需要被锁定时才加锁。(之所以会有这种差异,是因为有些行可能会被本地检查的限制条件或连接条件丢弃。)早期锁定要简单得多,并且避免了与远程存储之间的额外往返,但它可能导致某些本来无需锁定的行也被锁定,从而降低并发性,甚至引发意外的死锁。此外,只有在后续还能唯一重新识别出需要加锁的那一行时,晚期锁定才是可行的。理想情况下,行标识符应能标识该行的某个特定版本,就像 PostgreSQL 的 TID 那样。
默认情况下,PostgreSQL 在与 FDW 交互时不会考虑锁定问题,但 FDW 可以在没有核心代码显式支持的情况下执行早期锁定。Section 55.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 55.2.6中定义的回调函数。在GetForeignRowMarkType中,应根据所请求的锁强度选择 ROW_MARK_EXCLUSIVE、ROW_MARK_NOKEYEXCLUSIVE、ROW_MARK_SHARE 或 ROW_MARK_KEYSHARE 行标记选项。(无论选择这四者中的哪一个,核心代码的行为都是相同的。)在其他地方,可以在规划时使用get_plan_rowmark,或者在执行时使用ExecFindRowMark,来检测某张外部表是否被此类命令指定为需要加锁;你不仅要检查是否返回了非空的行标记结构,还必须检查其 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 的注释。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。