PostgreSQL 使用唯一索引强制执行 SQL 唯一性约束,这些索引不允许存在具有相同键的多个条目。支持此功能的访问方法将 amcanunique
设置为 true。(目前,只有 b 树支持它。)在强制执行唯一性时,不会考虑 INCLUDE
子句中列出的列。
由于 MVCC,总是需要在索引中物理上允许存在重复条目:这些条目可能引用单个逻辑行的连续版本。我们实际想要强制执行的行为是,没有 MVCC 快照可以包含两个具有相等索引键的行。这可以分解为以下在将新行插入唯一索引时必须检查的情况:
如果当前事务已删除冲突的有效行,则没问题。(特别是,由于 UPDATE 总是在插入新版本之前删除旧的行版本,这将允许在不更改键的情况下对行进行 UPDATE。)
如果冲突的行是由尚未提交的事务插入的,则将要插入的行必须等待以查看该事务是否提交。如果回滚,则没有冲突。如果提交时不再次删除冲突的行,则存在唯一性违规。(在实践中,我们只是等待其他事务结束,然后重新进行整体可见性检查。)
类似地,如果冲突的有效行已被尚未提交的事务删除,则将要插入的行必须等待该事务提交或中止,然后重复测试。
此外,根据上述规则报告唯一性违规之前,访问方法必须重新检查要插入行的活动状态。如果已提交死亡,则不应报告违规。(在当前事务刚刚创建的行的一般插入场景中,不会发生这种情况。但是,在 CREATE UNIQUE INDEX CONCURRENTLY
期间可能会发生。)
我们要求索引访问方法自行应用这些测试,这意味着它必须进入堆以检查根据索引内容显示具有重复键的任何行的提交状态。毫无疑问,这是丑陋且非模块化的,但它可以节省冗余工作:如果我们进行单独的探测,那么在查找要插入新行索引条目的位置时,对冲突行的索引查找将基本上重复进行。更重要的是,除非冲突检查是新索引条目插入的组成部分,否则没有明显的方法可以避免竞争条件。
如果唯一约束是可延迟的,则会增加复杂性:我们需要能够为新行插入索引条目,但将任何唯一性违规错误延迟到语句末尾甚至更晚。为了避免不必要的索引重复搜索,索引访问方法应在初始插入期间进行初步唯一性检查。如果这表明绝对没有冲突的活动元组,那么我们就完成了。否则,我们安排在强制执行约束时进行重新检查。如果在重新检查时,插入的元组和某个其他具有相同键的元组都是活动的,则必须报告错误。(请注意,为此目的,“活动”实际上意味着“索引条目的 HOT 链中的任何元组都是活动的”。)为了实现此目的,将向 aminsert
函数传递一个 checkUnique
参数,该参数具有以下值之一:
UNIQUE_CHECK_NO
表示不应进行唯一性检查(这不是唯一索引)。
UNIQUE_CHECK_YES
表示这是一个不可延迟的唯一索引,并且必须立即执行唯一性检查,如上所述。
UNIQUE_CHECK_PARTIAL
表示唯一约束是可延迟的。PostgreSQL 将使用此模式来插入每行的索引条目。访问方法必须允许重复条目进入索引,并通过从 aminsert
返回 false 来报告任何潜在的重复项。对于每个返回 false 的行,将安排延迟的重新检查。
访问方法必须识别任何可能违反唯一约束的行,但报告误报不是错误。这允许在不等待其他事务完成的情况下完成检查;此处报告的冲突不被视为错误,稍后将重新检查,届时它们可能不再是冲突。
UNIQUE_CHECK_EXISTING
表示这是对报告为潜在唯一性违规的行进行的延迟重新检查。尽管这是通过调用 aminsert
实现的,但访问方法在这种情况下不得插入新的索引条目。索引条目已经存在。相反,访问方法必须检查是否存在另一个活动的索引条目。如果是,并且如果目标行仍然是活动的,则报告错误。
建议在 UNIQUE_CHECK_EXISTING
调用中,访问方法进一步验证目标行实际上在索引中是否确实存在条目,如果不存在则报告错误。这是一个好主意,因为传递给 aminsert
的索引元组值将被重新计算。如果索引定义涉及并非真正不可变的函数,则我们可能正在检查索引的错误区域。检查在重新检查中是否找到了目标行可以验证我们是否正在扫描与原始插入中使用的相同的元组值。
如果您在文档中发现任何不正确、与您使用特定功能的体验不符或需要进一步澄清的地方,请使用此表单来报告文档问题。