61.4. 索引锁定注意事项

索引访问方法必须处理多个进程同时进行的索引更新。核心 PostgreSQL 系统在索引扫描期间在索引上获得AccessShareLock,而在更新索引时获得RowExclusiveLock(包括纯VACUUM)。由于这些锁类型没有冲突,因此访问方法负责处理可能需要的任何细粒度的锁。整个索引的排他锁仅在索引创建,销毁或REINDEX期间进行。

构建支持并发更新的索引类型通常需要对所需行为进行广泛而细微的分析。对于 b 树和哈希索引类型,您可以阅读有关src/backend/access/nbtree/READMEsrc/backend/access/hash/README的设计决策。

除了索引自身的内部一致性要求之外,并发更新还会创建有关父表(* heap *)和索引之间一致性的问题。因为 PostgreSQL 将堆的访问和更新与索引的访问和更新分开,所以在某些窗口中索引可能与堆不一致。我们使用以下规则处理此问题:

  • 在创建其索引条目之前,将创建一个新的堆条目。 (因此,并发索引扫描很可能无法看到堆条目.这是可以的,因为无论如何索引索引读取器对未提交的行都不感兴趣.但是请参见Section 61.5。)

  • 当要删除堆条目(通过VACUUM)时,必须首先删除其所有索引条目。

  • 索引扫描必须在索引页上保持一个钉子,该钉子上保存着amgettuple最后返回的项目,而ambulkdelete无法从其他后端钉住的页面中删除条目。下面说明了此规则的必要性。

如果没有第三条规则,索引读取器就有可能在被VACUUM删除索引条目之前看到索引条目,然后在被VACUUM删除索引条目之后到达相应的堆条目。如果该项目号在阅读器到达时仍未使用,则不会造成严重问题,因为heap_fetch()将忽略空的项目槽。但是,如果第三个后端已经将物品槽重新用于其他用途,该怎么办?使用兼容 MVCC 的快照时,没有问题,因为插槽的新占用者肯定太新而无法通过快照测试。但是,使用不符合 MVCC 的快照(例如SnapshotAny),可以接受并返回实际上与扫描键不匹配的行。我们可以通过要求在所有情况下都针对堆行重新检查扫描键来防御这种情况,但这太昂贵了。取而代之的是,我们使用索引页上的图钉作为代理,以指示读取器可能仍在从索引条目到匹配堆条目的“飞行中”。在这样的引脚上设置ambulkdelete块可确保VACUUM在读取器完成之前无法删除堆条目。该解决方案的运行时间很少,并且仅在极少数情况下实际存在冲突时才增加阻塞开销。

此解决方案要求索引扫描是“同步的”:我们必须在扫描相应的索引条目后立即获取每个堆 Tuples。由于许多原因,这很昂贵。 “异步”扫描中,我们从索引中收集了许多 TID,并且仅在以后的某个时间访问堆 Tuples,它需要的索引锁定开销要少得多,并且可以允许使用更有效的堆访问模式。根据以上分析,我们必须对不符合 MVCC 的快照使用同步方法,但是对于使用 MVCC 快照的查询,异步扫描是可行的。

amgetbitmap索引扫描中,访问方法不会在返回的任何 Tuples 上保留索引销。因此,仅将此类扫描与兼容 MVCC 的快照一起使用是安全的。

如果未设置ampredlocks标志,则在可序列化事务中使用该索引访问方法进行的任何扫描都将获得对完整索引的无阻塞谓词锁定。这将与并发可序列化事务将任何 Tuples 插入该索引中产生读写冲突。如果在一组并发可序列化事务中检测到某些模式的读写冲突,则可以取消其中一个事务以保护数据完整性。设置该标志时,表明索引访问方法实现了更细粒度的谓词锁定,这将倾向于减少此类事务取消的频率。