受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

62.5. 索引唯一性检查 #

PostgreSQL 使用唯一索引来强制执行 SQL 唯一性约束;所谓唯一索引,就是不允许存在多个具有相同键值条目的索引。支持这一特性的访问方法会把 amcanunique 设为真。(目前只有 B-树支持这一点。)在强制唯一性时,不会考虑 INCLUDE 子句中列出的列。

由于 MVCC 的存在,索引中在物理上总是必须允许存在重复条目:这些条目可能指向同一个逻辑行的连续版本。我们真正想强制的行为是,任何 MVCC 快照都不能同时包含两行拥有相同索引键的记录。在向唯一索引插入一行新数据时,必须检查以下几种情况:

  • 如果某一条冲突的有效行已被当前事务删除,那么这是允许的。(特别是,因为一次 UPDATE 总是在插入新版本前删除旧版本,这就允许对某一行执行不改变键值的 UPDATE。)

  • 如果某条冲突的行是由一个尚未提交的事务插入的,那么当前准备插入的事务必须等待,看看那个事务是否提交。如果它回滚,就不存在冲突;如果它提交并且没有再次删除那条冲突行,就发生了唯一性违背。(实际上,我们只是等待另一个事务结束,然后把可见性检查整个重新做一遍。)

  • 类似地,如果某条冲突的有效行是由一个尚未提交的事务删除的,那么当前准备插入的事务必须等待该事务提交或中止,然后重新执行测试。

此外,在按照上述规则报告唯一性违背之前,访问方法必须立即重新检查正在插入那一行的存活性。如果它已经是提交后死亡的状态,就不应报告违背。(这种情况不会出现在插入由当前事务刚创建的行这一普通场景中,但在 CREATE UNIQUE INDEX CONCURRENTLY 期间却可能发生。)

我们要求索引访问方法自行应用这些测试,这意味着它必须深入堆中检查那些根据索引内容显示为具有重复键的行的提交状态。毫无疑问,这样做既丑陋又不够模块化,但它避免了重复工作:如果我们单独再做一次探测,那么在寻找新行索引条目插入位置时,查找冲突行的索引搜索实际上就会被重复执行。更何况,除非把冲突检查作为插入新索引条目动作的一个组成部分,否则也没有明显的方法可以避免竞争条件。

如果唯一约束是可延迟的,情况会更复杂:我们需要能够为新行插入一个索引条目,但把任何唯一性违背错误延迟到语句结束时甚至更晚才报告。为了避免对索引进行不必要的重复搜索,索引访问方法应在初始插入期间执行一次初步唯一性检查。如果这表明确实不存在冲突的存活元组,那么事情就结束了。否则,我们会安排在真正强制约束时再做一次重检。若在重检时,插入的元组与另外某个具有相同键值的元组都仍然存活,就必须报告错误。(注意,就此用途而言,存活实际上是指索引条目 HOT 链中的任一元组是存活的。)为实现这一点,传给 aminsert 函数的 checkUnique 参数会取以下值之一:

  • UNIQUE_CHECK_NO 表示不应执行唯一性检查(这不是唯一索引)。

  • UNIQUE_CHECK_YES 表示这是一个不可延迟的唯一索引,必须按前述方式立即执行唯一性检查。

  • UNIQUE_CHECK_PARTIAL 表示该唯一约束是可延迟的。PostgreSQL 会使用此模式为每一行插入索引条目。访问方法必须允许索引中出现重复条目,并通过让 aminsert 返回假来报告任何潜在的重复。对于每一行返回假的情况,系统都会安排一次延迟重检。

    访问方法必须识别任何可能违反唯一约束的行,但报告误报并不算错误。这使得检查可以在不等待其他事务结束的情况下完成;此处报告的冲突不会被当作错误处理,而会在之后重新检查,到那时它们可能已不再冲突。

  • UNIQUE_CHECK_EXISTING 表示这是对某一行的延迟重检,此前该行被报告为可能违反唯一性。虽然这同样是通过调用 aminsert 来实现,但访问方法在这种情况下不得插入新的索引条目。该索引条目已经存在。相反,访问方法必须检查是否存在另一个存活的索引条目;如果存在,并且目标行本身也仍然存活,就应报告错误。

    建议在一次 UNIQUE_CHECK_EXISTING 调用中,访问方法进一步验证目标行在索引中确实已有现存条目;若没有,就报告错误。这样做是个好主意,因为传给 aminsert 的索引元组值会被重新计算。如果索引定义涉及并非真正 immutable 的函数,我们就可能在索引的错误区域上做检查。确认重检时确实找到了目标行,可以验证我们扫描的仍是原始插入时使用的同一组元组值。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。