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