索引访问方法必须处理多个进程对索引的并发更新。核心 PostgreSQL 系统在索引扫描期间获取索引上的 AccessShareLock
,在更新索引时获取 RowExclusiveLock
(包括普通的 VACUUM
)。由于这些锁类型不冲突,因此访问方法负责处理其可能需要的任何细粒度锁定。索引整体的 ACCESS EXCLUSIVE
锁仅在索引创建、销毁或 REINDEX
期间获取(使用 CONCURRENTLY
时,改为获取 SHARE UPDATE EXCLUSIVE
)。
构建支持并发更新的索引类型通常需要对所需行为进行广泛而细致的分析。对于 b 树和哈希索引类型,您可以在 src/backend/access/nbtree/README
和 src/backend/access/hash/README
中阅读有关设计决策的信息。
除了索引自身的内部一致性要求外,并发更新还会在父表(堆)和索引之间产生一致性问题。因为 PostgreSQL 将堆的访问和更新与索引的访问和更新分开,所以存在索引可能与堆不一致的窗口。我们使用以下规则来解决这个问题
在创建索引项之前先创建新的堆项。(因此,并发索引扫描可能无法看到堆项。这没关系,因为索引读取器本来就不关心未提交的行。但请参见 第 62.5 节。)
当要删除堆项时(通过 VACUUM
),必须先删除其所有索引项。
索引扫描必须维护对包含 amgettuple
最后返回的项的索引页面的锁定,并且 ambulkdelete
无法删除由其他后端锁定的页面的项。需要此规则的原因将在下面说明。
如果没有第三条规则,索引读取器可能在 VACUUM
删除索引项之前看到该项,然后在 VACUUM
删除对应堆项之后到达该项。如果该项号在读取器到达时仍未被使用,则不会产生严重问题,因为空项槽将被 heap_fetch()
忽略。但如果第三个后端已将该项槽重新用于其他用途怎么办?当使用 MVCC 兼容快照时,不会出现问题,因为槽的新占用者肯定太新,无法通过快照测试。但是,如果使用非 MVCC 兼容快照(例如 SnapshotAny
),则有可能接受并返回实际上与扫描键不匹配的行。我们可以通过要求在所有情况下重新检查扫描键与堆行是否匹配来防御这种情况,但这太昂贵了。相反,我们使用对索引页面的锁定作为代理来指示读取器可能仍从索引项“飞行”到匹配的堆项。使 ambulkdelete
阻塞这种锁定可以确保 VACUUM
无法在读取器完成之前删除堆项。此解决方案在运行时几乎没有成本,并且仅在实际上发生冲突的罕见情况下才会增加阻塞开销。
此解决方案要求索引扫描是“同步的”:我们必须在扫描相应的索引项后立即获取每个堆元组。这由于以下几个原因而变得昂贵。在“异步”扫描中,我们会从索引中收集许多 TID,然后才访问堆元组,这会大大减少索引锁定开销,并允许更有效的堆访问模式。根据以上分析,我们必须对非 MVCC 兼容快照使用同步方法,但对使用 MVCC 快照的查询,异步扫描是可行的。
在 amgetbitmap
索引扫描中,访问方法不会对任何返回的元组保留索引锁定。因此,仅当使用 MVCC 兼容快照时才安全使用这种扫描。
当 ampredlocks
标志未设置时,在可序列化事务中使用该索引访问方法的任何扫描都将在完整索引上获取非阻塞谓词锁。这将与并发可序列化事务将任何元组插入该索引产生读写冲突。如果在一组并发可序列化事务中检测到某些模式的读写冲突,则可能会取消其中一个事务以保护数据完整性。当设置该标志时,它表示索引访问方法实现了更细粒度的谓词锁定,这将减少这种事务取消的频率。
如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用 此表格 报告文档问题。