支援版本:目前 (16) / 15 / 14 / 13 / 12
開發版本:devel
不支援版本:11

67.4. 實作 #

本節涵蓋 B 樹索引實作的詳細資料,供進階使用者參考。請參閱原始程式碼散佈中的 src/backend/access/nbtree/README,以取得 B 樹實作更詳細且著重於內部的說明。

67.4.1. B 樹結構 #

PostgreSQL B 樹索引是多層級樹狀結構,其中樹的每一層都可用作頁面的雙向連結清單。單一元資料頁面儲存在索引的第一個區段檔案開頭的固定位置。所有其他頁面都是葉面或內頁。葉面是樹狀結構最低層級的頁面。所有其他層級都包含內頁。每個葉面包含指向資料列的組。每個內頁包含指向樹狀結構下一個層級的組。通常,超過 99% 的所有頁面都是葉面。內頁和葉面都使用 第 73.6 節 中說明的標準頁面格式。

當現有葉面無法容納新進組時,會將新的葉面新增到 B 樹索引。頁面分割 操作會將原本屬於溢位頁面的項目移到新頁面,為這些項目騰出空間。頁面分割也必須在父頁面中插入指向新頁面的新下行連結,這可能會導致父頁面也分割。頁面分割會以遞迴方式向上串接。當根頁面最終無法容納新的下行連結時,就會執行根頁面分割 操作。這會透過建立一個比原本根頁面高一層的新根頁面,為樹狀結構新增一個新層級。

67.4.2. 自底向上索引刪除 #

B 樹索引並未直接意識到在 MVCC 下,同一個邏輯資料表列可能有多個現存版本;對索引而言,每個元組都是一個獨立的物件,需要自己的索引項目。有時,「版本變動」元組可能會累積,並對查詢延遲和傳輸量產生負面影響。這通常發生在以「更新」為主的負載中,其中大多數個別更新無法套用HOT 最佳化。在「更新」期間僅變更一個索引涵蓋的單一欄位值時,總是需要一組新的索引元組,每個索引元組對應資料表上的每個索引。特別注意,這包括未經「更新」邏輯修改的索引。所有索引都需要一個後繼實體索引元組,指向資料表中的最新版本。每個索引中的每個新元組通常需要與原始已更新元組共存一段時間(通常在「更新」交易提交後不久)。

B 樹索引透過執行由下而上的索引刪除傳遞,逐步刪除版本變動索引元組。每次刪除傳遞都是針對預期的版本變動頁面分割觸發。這只會發生在未經「更新」陳述邏輯修改的索引中,否則特定頁面中過時的版本會集中累積。通常會避免頁面分割,但某些實作層級啟發法可能會無法辨識並刪除甚至一個垃圾索引元組(這種情況下,頁面分割或重複資料刪除傳遞會解決傳入新元組不符合葉面大小的問題)。任何索引掃描必須橫越的版本數(對於任何單一邏輯列)的最糟情況,是整體系統回應性與傳輸量的重要因素。由下而上的索引刪除傳遞針對單一葉面中的可疑垃圾元組,根據涉及邏輯列和版本的定性區別。這與自動真空工作常式執行的由上而下索引清理形成對比,後者是在超過特定定量資料表層級閾值時觸發(請參閱第 25.1.6 節)。

注意

並非所有在 B 樹索引中執行的刪除操作都是由下而上的刪除操作。索引元組刪除有一個不同的類別:簡單索引元組刪除。這是一個延遲維護操作,用於刪除已知可以安全刪除的索引元組(其項目識別碼的 LP_DEAD 位元已設定)。與由下而上的索引刪除類似,簡單索引刪除會在預期頁面分割時進行,作為避免分割的方法。

簡單刪除在機會主義的意義上,它只能在最近的索引掃描順便設定受影響項目的 LP_DEAD 位元時進行。在 PostgreSQL 14 之前,唯一的 B 樹刪除類別是簡單刪除。它與由下而上的刪除之間的主要差異在於,只有前者是由經過索引掃描的活動機會主義驅動,而只有後者特別針對不會邏輯修改索引欄位的 UPDATE 版本變動。

由下而上的索引刪除會針對具有特定工作負載的特定索引執行絕大多數的全部垃圾索引元組清理。對於任何受到 UPDATE 版本變動顯著影響的 B 樹索引,這是預期的,而 UPDATE 很少或從不邏輯修改索引涵蓋的欄位。每個邏輯列的平均版本數和最差情況版本數可以純粹透過目標遞增刪除傳遞來保持在低度。儘管 UPDATE持續 的版本變動,某些索引的磁碟大小很有可能永遠不會增加一個單一頁面/區塊。即使如此,VACUUM 操作(通常在自動真空工作程序中執行)的徹底 徹底清理 最終將作為表格及其每個索引的 整體 清理的一部分而需要。

VACUUM 不同,由下而上的索引刪除不會提供任何關於最舊的垃圾索引元組可能有多舊的強有力保證。不允許任何索引保留在表格和其所有索引共同共享的保守截止點之前已變為無效的 浮動垃圾 索引元組。這個基本的表格層級不變式讓回收表格 TID 變得安全。這讓不同的邏輯列隨著時間重複使用相同的表格 TID(儘管這永遠不會發生在兩個邏輯列的生命週期跨越同一個 VACUUM 週期的情況)。

67.4.3. 重複資料刪除 #

重複資料是一個葉面頁面元組(指向表格列的元組),其中 所有 索引鍵欄位的值與同一個索引中至少一個其他葉面頁面元組的對應欄位值相符。在實務上,重複元組相當常見。當啟用一個選用技術時,B 樹索引可以使用一個特殊的、空間效率高的重複資料表示法:重複資料刪除

重複資料移除會定期合併重複組別的元組,為每個組別建立單一的張貼清單元組。欄位金鑰值只會在此表示中出現一次。接著會出現一個已排序的TID陣列,指向表格中的列。這會大幅減少索引的儲存大小,其中每個值(或每個欄位值的獨特組合)平均會出現好幾次。查詢的延遲時間會大幅減少。整體查詢處理量可能會大幅增加。例行索引真空吸塵的負擔也可能會大幅減少。

注意

B 樹重複資料移除對包含 NULL 值的重複資料一樣有效,即使根據任何 B 樹運算子類別的=成員,NULL 值永遠不會等於彼此。就實作的任何部分而言,只要了解磁碟上的 B 樹結構,NULL 就只是索引值網域中的另一個值。

重複資料移除程序會在無法放入現有葉子頁面的新項目插入時延遲執行,但僅在索引元組刪除無法為新項目釋出足夠空間時(通常會簡短考慮刪除,然後略過)。與 GIN 張貼清單元組不同,B 樹張貼清單元組不需要在每次插入新重複資料時擴充;它們只是葉子頁面原始邏輯內容的另一種實體表示。此設計優先考量混合讀寫工作負載的一致效能。大多數用戶端應用程式至少會從使用重複資料移除中看到中等的效能提升。重複資料移除預設為啟用。

CREATE INDEXREINDEX會套用重複資料移除來建立張貼清單元組,但它們使用的策略略有不同。從表格取得的已排序輸入中遇到的每個重複一般元組組別會合併成一個張貼清單元組,加入目前的待處理葉子頁面之前。個別張貼清單元組會打包盡可能多的TID。葉子頁面會以一般方式寫出,不會有額外的重複資料移除傳遞。此策略非常適合CREATE INDEXREINDEX,因為它們是單次批次作業。

寫入密集型工作負載不會因為索引中重複值很少或沒有而從重複資料移除中受益,會產生輕微的固定效能損失(除非重複資料移除已明確停用)。deduplicate_items儲存參數可用於停用個別索引中的重複資料移除。讀取專用工作負載永遠不會有效能損失,因為讀取張貼清單元組至少和讀取標準元組表示一樣有效率。停用重複資料移除通常沒有幫助。

有時,唯一索引(以及唯一約束)可以使用重複資料移除。這允許葉子頁面暫時吸收額外的版本變更重複資料。唯一索引中的重複資料移除會擴充由下而上的索引刪除,特別是在長期執行交易保留會阻擋垃圾收集的快照時。目標是為由下而上的索引刪除策略爭取時間,讓它再次生效。延後頁面分割,直到單一長期執行交易自然消失,可以讓由下而上的刪除傳遞成功,而較早的刪除傳遞會失敗。

提示

套用一個特殊啟發法來確定是否應在唯一索引中執行重複資料刪除。它通常可以跳過直接分割葉子頁面,避免浪費週期在無益的重複資料刪除上而造成效能損失。如果您擔心重複資料刪除的開銷,請考慮選擇性地設定 deduplicate_items = off。在唯一索引中啟用重複資料刪除幾乎沒有缺點。

由於實作層級限制,並非所有情況都可使用重複資料刪除。重複資料刪除安全性是在執行 CREATE INDEXREINDEX 時確定的。

請注意,在涉及相等資料之間有語意上顯著差異的下列情況中,重複資料刪除被視為不安全且無法使用

  • textvarcharchar 在使用 非確定性 校對時無法使用重複資料刪除。相等資料之間必須保留大小寫和重音差異。

  • numeric 無法使用重複資料刪除。相等資料之間必須保留數字顯示比例。

  • jsonb 無法使用重複資料刪除,因為 jsonb B 樹運算子類別在內部使用 numeric

  • float4float8 無法使用重複資料刪除。這些類型對 -00 有不同的表示法,但仍被視為相等。必須保留此差異。

有一個進一步的實作層級限制,可能會在 PostgreSQL 的未來版本中解除

  • 容器類型(例如複合類型、陣列或範圍類型)無法使用重複資料刪除。

有一個進一步的實作層級限制,適用於不論使用哪個運算子類別或校對

  • INCLUDE 索引永遠無法使用重複資料刪除。

提交更正

如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用 此表單 報告文件問題。