61.5. 索引唯一性检查

PostgreSQL 使用* unique index *强制执行 SQL 唯一性约束,这是不允许使用相同键的多个条目的索引。支持此功能的访问方法将amcanunique设置为 true。 (当前,只有 b 树支持它.)强制唯一性时,不考虑INCLUDE子句中列出的列。

由于存在 MVCC,始终必须允许索引中的重复条目物理存在:这些条目可能引用单个逻辑行的连续版本。我们实际上要强制执行的行为是,任何 MVCC 快照都不能包含具有相等索引键的两行。这分为以下几种情况,在将新行插入唯一索引时必须检查以下情况:

  • 如果当前事务已删除有冲突的有效行,则可以。 (特别是,由于 UPDATE 在插入新版本之前总是删除旧行版本,因此这将允许对一行进行 UPDATE 而无需更改键.)

  • 如果尚未提交的事务已插入冲突的行,则潜在插入程序必须 await 以查看该事务是否已提交。如果回滚,则没有冲突。如果提交但未再次删除冲突的行,则表示存在唯一性冲突。 (实际上,我们只是 await 其他事务结束,然后重新执行可见性检查.)

  • 同样,如果尚未提交的事务删除了有冲突的有效行,则将要插入的事务必须 await 该事务提交或中止,然后重复测试。

此外,在根据上述规则报告唯一性违规之前,访问方法必须重新检查要插入的行的活动性。如果它被判死刑,则不应报告任何违规行为。 (这种情况在插入由当前事务刚创建的行的普通情况下不会发生.但是,可能在CREATE UNIQUE INDEX CONCURRENTLY期间发生.)

我们需要索引访问方法本身来应用这些测试,这意味着它必须到达堆中才能检查根据索引内容显示为具有重复键的任何行的提交状态。无疑这是丑陋且非模块化的,但它节省了多余的工作:如果我们进行了单独的探测,则在寻找插入新行的索引条目的位置时,实质上将重复对冲突行的索引查找。而且,除非冲突检查是插入新索引条目的组成部分,否则没有明显的方法可以避免出现争用情况。

如果唯一性约束是可延迟的,则将存在额外的复杂性:我们需要能够为新行插入索引条目,但是将任何违反唯一性的错误推迟到语句结束时或更晚。为避免不必要的重复搜索索引,索引访问方法应在初始插入期间进行初步的唯一性检查。如果这表明绝对没有冲突的活动 Tuples,那么我们就完成了。否则,我们计划在应该执行约束时进行一次重新检查。如果在重新检查时插入的 Tuples 和其他具有相同键的 Tuples 都处于活动状态,则必须报告错误。 (请注意,为此,“活动”实际上表示“索引条目的 HOT 链中的任何 Tuples 都是活动的”.)要实现这一点,将为aminsert函数传递一个具有以下值之一的checkUnique参数:

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

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

  • UNIQUE_CHECK_PARTIAL表示唯一约束是可延迟的。 PostgreSQL 将使用此模式插入每一行的索引条目。访问方法必须允许重复的条目进入索引,并通过从aminsert返回 false 来报告任何可能的重复项。对于返回 false 的每一行,将安排推迟的重新检查。

访问方法必须标识任何可能违反唯一约束的行,但是报告误报并不是错误。这样就可以完成检查而无需 await 其他事务完成。此处报告的冲突不会被视为错误,稍后会再次检查,到那时它们可能不再是冲突。

  • UNIQUE_CHECK_EXISTING表示这是对报告为潜在唯一性违规的行的延迟重新检查。尽管这是通过调用aminsert实现的,但是在这种情况下,访问方法必须不要插入新的索引条目。索引条目已经存在。而是,访问方法必须检查以查看是否还有另一个活动索引条目。如果是这样,并且目标行也仍然存在,请报告错误。

建议在UNIQUE_CHECK_EXISTING调用中,访问方法进一步验证目标行是否确实在索引中具有现有条目,如果没有,则报告错误。这是个好主意,因为传递给aminsert的索引 Tuples 值将被重新计算。如果索引定义涉及的功能并不是 true 不变的,那么我们可能正在检查索引的错误区域。检查是否在重新检查中找到了目标行,从而验证我们是否在扫描与原始插入中使用的相同的 Tuples 值。