支援的版本:目前版本 (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

62.5. 索引唯一性檢查 #

PostgreSQL 使用唯一索引強制執行 SQL 唯一性約束,這些索引不允許具有相同鍵的多個條目。支援此功能的存取方法將 amcanunique 設為 true。(目前,只有 b-tree 支援它。)在強制執行唯一性時,不會考慮 INCLUDE 子句中列出的欄位。

由於 MVCC,始終需要在索引中物理存在重複的條目:這些條目可能引用單個邏輯列的連續版本。我們實際要強制執行的行為是,沒有 MVCC 快照可以包含具有相等索引鍵的兩列。這可以分解為以下案例,在將新列插入唯一索引時必須檢查這些案例

  • 如果衝突的有效列已被當前交易刪除,則沒關係。(特別是,由於 UPDATE 始終在插入新版本之前刪除舊列版本,這將允許在不更改鍵的情況下更新列。)

  • 如果衝突的列已由尚未提交的交易插入,則潛在的插入者必須等待查看該交易是否提交。如果回滾,則沒有衝突。如果它提交時沒有再次刪除衝突的列,則存在唯一性衝突。(實際上,我們只是等待另一個交易結束,然後完全重新進行可見性檢查。)

  • 同樣,如果衝突的有效列已被尚未提交的交易刪除,則潛在的插入者必須等待該交易提交或中止,然後重複測試。

此外,在根據上述規則報告唯一性衝突之前,存取方法必須重新檢查正在插入的列的存活性。如果它已提交死亡,則不應報告衝突。(在插入剛由當前交易創建的列的普通情況下,不會發生這種情況。但是,在使用 CREATE UNIQUE INDEX CONCURRENTLY 時可能會發生。)

我們要求索引存取方法自己應用這些測試,這意味著它必須進入堆疊以檢查任何顯示具有重複鍵的列的提交狀態(根據索引內容)。毫無疑問,這是醜陋且非模組化的,但它可以節省冗餘工作:如果我們進行單獨的探測,那麼在查找插入新列的索引條目的位置時,基本上會重複查找衝突列的索引。更重要的是,除非衝突檢查是插入新索引條目的組成部分,否則沒有明顯的方法可以避免競爭條件。

如果唯一性約束是可延遲的,則會增加複雜性:我們需要能夠為新列插入索引條目,但將任何唯一性衝突錯誤延遲到語句結束甚至更晚。為了避免不必要的重複搜尋索引,索引存取方法應在初始插入期間執行初步的唯一性檢查。如果這表明絕對沒有衝突的活動元組,我們就完成了。否則,我們安排在強制執行約束時發生重新檢查。如果在重新檢查時,插入的元組和具有相同鍵的其他一些元組都是活動的,則必須報告錯誤。(請注意,就此而言,活動實際上意味著索引條目的 HOT 鏈中的任何元組都是活動的。)為了實現這一點,將一個 checkUnique 參數傳遞給 aminsert 函數,該參數具有以下值之一

  • UNIQUE_CHECK_NO 表示不應進行唯一性檢查(這不是唯一索引)。

  • UNIQUE_CHECK_YES 表示這是一個不可延遲的唯一索引,必須立即執行唯一性檢查,如上所述。

  • UNIQUE_CHECK_PARTIAL 表示唯一性約束是可延遲的。PostgreSQL 將使用此模式插入每個列的索引條目。存取方法必須允許在索引中存在重複的條目,並透過從 aminsert 傳回 false 來報告任何潛在的重複項。對於傳回 false 的每一列,將安排延遲的重新檢查。

    存取方法必須識別任何可能違反唯一性約束的列,但報告誤報不是錯誤。這允許在不等待其他交易完成的情況下完成檢查;此處報告的衝突不被視為錯誤,並將在稍後重新檢查,屆時它們可能不再是衝突。

  • UNIQUE_CHECK_EXISTING 表示這是對報告為潛在唯一性衝突的列的延遲重新檢查。雖然這是透過呼叫 aminsert 實現的,但在這種情況下,存取方法不得插入新的索引條目。索引條目已經存在。相反,存取方法必須檢查是否存在另一個活動的索引條目。如果是,並且目標列仍然是活動的,則報告錯誤。

    建議在 UNIQUE_CHECK_EXISTING 呼叫中,存取方法進一步驗證目標列在索引中確實存在條目,如果沒有,則報告錯誤。 這是一個好主意,因為傳遞給 aminsert 的索引元組值將被重新計算。 如果索引定義包含並非真正不可變的函數,我們可能正在檢查索引的錯誤區域。 檢查是否在重新檢查中找到了目標列,可以驗證我們掃描的是與原始插入中使用的相同的元組值。

提交更正

如果您在文件中發現任何不正確、與您特定功能的使用經驗不符或需要進一步說明的內容,請使用此表單來回報文件問題。