索引訪問方法必須處理來自多個程序的索引的併發更新。核心 PostgreSQL 系統在索引掃描期間獲取索引的 AccessShareLock
,在更新索引時(包括普通 VACUUM
)獲取 RowExclusiveLock
。由於這些鎖型別不衝突,因此訪問方法負責處理其可能需要的任何細粒度鎖定。在索引建立、銷燬或 REINDEX
期間才會獲取整個索引的 ACCESS EXCLUSIVE
鎖(使用 CONCURRENTLY
時則獲取 SHARE UPDATE EXCLUSIVE
)。
構建一個支援併發更新的索引型別通常需要對所需行為進行廣泛而細緻的分析。對於 b-tree 和 hash 索引型別,您可以在 src/backend/access/nbtree/README
和 src/backend/access/hash/README
中閱讀相關的設計決策。
除了索引自身的內部一致性要求外,併發更新還會引發關於父表(堆)和索引之間一致性的問題。由於 PostgreSQL 將堆的訪問和更新與索引的訪問和更新分開,因此存在索引可能與堆不一致的視窗。我們透過以下規則來處理此問題:
在建立其索引條目之前,會先建立新的堆條目。(因此,併發索引掃描可能無法看到堆條目。這沒關係,因為索引讀取器無論如何也不會關心未提交的行。但請參見 第 63.5 節。)
當要刪除堆條目時(透過 VACUUM
),必須先刪除其所有索引條目。
索引掃描必須保持對儲存 amgettuple
最後返回的條目的索引頁的固定,並且 ambulkdelete
不能刪除被其他後端鎖定的頁面中的條目。此規則的必要性將在下面解釋。
沒有第三條規則,索引讀取器就有可能在條目被 VACUUM
刪除之前看到該索引條目,然後在其對應的堆條目被 VACUUM
刪除之後才到達該堆條目。如果該條目編號在使用時仍然未使用,則不會產生嚴重問題,因為 heap_fetch()
會忽略一個空的條目槽。但是,如果第三方後端已經為其他內容重新使用了該條目槽呢?在使用符合 MVCC 的快照時,不會有問題,因為該槽的新佔用者肯定太新而無法透過快照測試。但是,使用不符合 MVCC 的快照(例如 SnapshotAny
)時,就有可能接受並返回一個實際上不匹配掃描鍵的行。我們可以透過要求在所有情況下都重新檢查掃描鍵與堆行的匹配情況來防範這種情況,但這成本太高。相反,我們將索引頁上的固定作為代理,以指示讀取器可能仍然在從索引條目到匹配堆條目的過程中 “進行中”。使 ambulkdelete
在這種固定上阻塞,可以確保 VACUUM
在讀取器完成之前不會刪除堆條目。此解決方案在執行時成本很小,並且僅在實際發生衝突的罕見情況下才增加阻塞開銷。
此解決方案要求索引掃描是 “同步” 的:我們必須在掃描相應的索引條目後立即獲取每個堆元組。這由於多種原因而成本高昂。一種 “非同步” 掃描,我們從索引中收集許多 TID,然後在稍後才訪問堆元組,需要更少的索引鎖定開銷,並且可以允許更有效的堆訪問模式。根據上述分析,對於不符合 MVCC 的快照,我們必須使用同步方法,但對於使用 MVCC 快照的查詢,非同步掃描是可行的。
在 amgetbitmap
索引掃描中,訪問方法不會對返回的任何元組保持索引固定。因此,只有在使用符合 MVCC 的快照時,這種掃描才是安全的。
當 ampredlocks
標誌未設定時,任何在該索引訪問方法中使用該索引的事務,在可序列化事務中,都將獲取對整個索引的非阻塞謂詞鎖定。這將與併發可序列化事務將任何元組插入到該索引中產生讀寫衝突。如果在可序列化事務集合中檢測到某些讀寫衝突模式,為了保護資料完整性,可能會取消其中一個事務。當設定了該標誌時,表示索引訪問方法實現了更細粒度的謂詞鎖定,這傾向於減少此類事務取消的頻率。
如果您在文件中發現任何不正確、與您在使用該特定功能時的實際體驗不符或需要進一步澄清的內容,請使用 此表單 報告文件問題。