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
的索引元组值将被重新计算。如果索引定义涉及实际上不是不可变的函数,我们可能会检查索引的错误区域。检查目标行是否在重新检查中找到,验证我们是否正在扫描与原始插入中使用的相同元组值。
如果您在文档中发现任何不正确的内容,不符合您对特定功能的体验,或者需要进一步说明,请使用 此表格 报告文档问题。