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
的索引元组值将被重新计算。如果索引定义涉及实际上并非不可变的函数,我们可能正在检查索引的错误区域。检查目标行在重新检查中被发现,以验证我们正在扫描的元组值与原始插入中使用的值相同。