2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1

63.5. 索引唯一性检查 #

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

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

  • 如果当前事务已删除冲突的有效行,则没关系。(特别是,因为 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 的索引元组值将被重新计算。如果索引定义涉及非真正不可变(immutable)的函数,我们可能会检查索引的错误区域。检查目标行在重新检查中是否被找到,可以验证我们是否正在扫描与原始插入中使用的元组值相同的元组值。

提交更正

如果您在文档中发现任何不正确、与您对特定功能的体验不符或需要进一步澄清的内容,请使用 此表格 报告文档问题。