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 / 8.0

66.2. TOAST #

本節概述了TOAST(超大屬性儲存技術,The Oversized-Attribute Storage Technique)。

PostgreSQL 使用固定頁面大小(通常為 8 kB),並且不允許元組跨多個頁面。因此,不可能直接儲存非常大的欄位值。為了克服這個限制,大型欄位值被壓縮和/或分解成多個物理行。這對使用者是透明的,對大多數後端程式碼只有很小的影響。這種技術被親切地稱為TOAST(或 切片面包以來最好的東西)。TOASTTOAST基礎設施也用於改進大型資料值在記憶體中的處理。

只有某些資料型別支援 TOASTTOAST—— 對於不能產生大欄位值的資料型別,沒有必要施加這種開銷。為了支援 TOASTTOAST,資料型別必須具有可變長度 (varlena) 表示,其中,通常情況下,任何儲存值的前四個位元組包含值的總長度(包括自身)。TOASTTOAST不限制資料型別表示的其餘部分。特殊表示統稱為 TOASTed 值,它們透過修改或重新解釋這個初始長度字來實現。因此,支援 TOASTTOAST-ed 資料型別的 C 級函式必須小心處理潛在的 TOASTTOASTed 輸入值:輸入可能直到被 解 TOAST 後才實際包含四位元組長度字和內容。(這通常透過在對輸入值進行任何操作之前呼叫 PG_DETOAST_DATUM 來完成,但在某些情況下,更高效的方法是可能的。有關更多詳細資訊,請參閱 第 36.13.1 節。)

TOASTTOAST 佔用了 varlena 長度字的兩個位(大端機器上的高位,小端機器上的低位),從而將 TOASTTOAST-able 資料型別值的邏輯大小限制為 1 GB(230 - 1 位元組)。當兩個位都為零時,該值是資料型別的普通未 TOASTTOASTed 值,並且長度字的其餘位給出資料項的總大小(包括長度字)以位元組為單位。當最高位或最低位被設定時,該值只有一個單位元組頭部而不是通常的四位元組頭部,並且該位元組的其餘位給出資料項的總大小(包括長度位元組)以位元組為單位。這種替代方案支援 127 位元組以下值的空間高效儲存,同時仍然允許資料型別在需要時增長到 1 GB。具有單位元組頭部的值不與任何特定邊界對齊,而具有四位元組頭部的值至少與四位元組邊界對齊;這種對齊填充的省略提供了額外的空間節省,對於短值來說非常重要。作為一種特殊情況,如果單位元組頭部的其餘位都為零(這對於自包含長度是不可能的),則該值是指向行外資料的指標,具有以下所述的幾種可能替代方案。這種 TOAST 指標 的型別和大小由儲存在資料項的第二個位元組中的程式碼決定。最後,當最高位或最低位清除但相鄰位設定時,資料項的內容已被壓縮,必須在使用前解壓縮。在這種情況下,四位元組長度字的其餘位給出壓縮資料項的總大小,而不是原始資料。請注意,行外資料也可能進行壓縮,但 varlena 頭部不會說明是否已發生壓縮 — TOASTTOAST指標的內容會說明這一點。

用於行內或行外壓縮資料的壓縮技術可以透過在 CREATE TABLEALTER TABLE 中設定 COMPRESSION 列選項來為每個列選擇。對於沒有明確設定的列,預設是在插入資料時查閱 default_toast_compression 引數。

如前所述,TOAST 有多種型別TOAST指標資料項。最古老和最常見的型別是指向行外資料,該資料儲存在與包含 TOASTTOAST指標資料項的表分離但關聯的 TOAST 中。這些 磁碟上的 指標資料項由 TOASTTOAST管理程式碼(在 access/common/toast_internals.c 中)在要儲存到磁碟的元組太大而無法按原樣儲存時建立。更多詳細資訊請參見 第 66.2.1 節。或者,TOASTTOAST指標資料項可以包含指向記憶體中其他位置出現的行外資料的指標。此類資料項必然是短期的,永遠不會出現在磁碟上,但它們對於避免複製和冗餘處理大型資料值非常有用。更多詳細資訊請參見 第 66.2.2 節

66.2.1. 行外、磁碟上的 TOAST 儲存 #

如果表的任何列是 TOASTTOAST-able,則該表將具有一個關聯的 TOASTTOAST表,其 OID 儲存在表的 pg_class.reltoastrelid 條目中。磁碟上的 TOASTTOASTed 值儲存在 TOASTTOAST表中,如下所述。

行外值被(如果使用壓縮,則在壓縮後)分成最多 TOAST_MAX_CHUNK_SIZE 位元組的塊(預設情況下,此值被選擇為四個塊行可以容納在一個頁面中,使其大約為 2000 位元組)。每個塊作為其所有者表所屬的 TOASTTOAST表中的單獨一行儲存。每個 TOASTTOAST表都包含列 chunk_id(標識特定的 TOASTTOASTed 值的一個 OID)、chunk_seq(該值中塊的序列號)和 chunk_data(塊的實際資料)。chunk_idchunk_seq 上的唯一索引提供了值的快速檢索。表示行外磁碟上 TOASTTOASTed 值的指標資料項因此需要儲存要查詢的 TOASTTOAST表的 OID 和特定值的 OID(其 chunk_id)。為了方便起見,指標資料項還儲存邏輯資料項大小(原始未壓縮資料長度)、物理儲存大小(如果應用了壓縮,則不同)以及使用的壓縮方法(如果有)。考慮到 varlena 頭部位元組,磁碟上的 TOASTTOAST指標資料項的總大小因此為 18 位元組,而不考慮所表示值的實際大小。

TOASTTOAST 管理程式碼僅在要儲存在表中的行值寬度超過 TOAST_TUPLE_THRESHOLD 位元組(通常為 2 kB)時才觸發。TOASTTOAST程式碼將壓縮和/或將欄位值移到行外,直到行值短於 TOAST_TUPLE_TARGET 位元組(通常也為 2 kB,可調整)或無法再獲得更多收益。在 UPDATE 操作期間,未更改欄位的值通常按原樣保留;因此,如果行中沒有行外值發生更改,則對具有行外值的行的 UPDATE 不會產生 TOASTTOAST成本。

TOASTTOAST 管理程式碼識別出四種不同的策略,用於在磁碟上儲存 TOASTTOAST-able 列:

  • PLAIN 阻止壓縮和行外儲存。這是非 TOASTTOAST-able 資料型別列的唯一可能策略。

  • EXTENDED 允許壓縮和行外儲存。這是大多數 TOASTTOAST-able 資料型別的預設值。將首先嚐試壓縮,如果行仍然太大,則進行行外儲存。

  • EXTERNAL 允許行外儲存但不允許壓縮。使用 EXTERNAL 將使寬 textbytea 列上的子字串操作更快(以增加儲存空間為代價),因為這些操作經過最佳化,可以在未壓縮時只獲取行外值所需的部件。

  • MAIN 允許壓縮但不允許行外儲存。(實際上,對於此類列仍將執行行外儲存,但僅作為最後的手段,當沒有其他方法可以使行足夠小以適應頁面時。)

傳遞給 -c 的每個TOAST每個 TOAST-able 資料型別都為其資料型別的列指定了預設策略,但給定表列的策略可以透過 ALTER TABLE ... SET STORAGE 進行更改。

TOAST_TUPLE_TARGET 可以透過 ALTER TABLE ... SET (toast_tuple_target = N) 為每個表進行調整。

與允許行值跨頁的更直接的方法相比,這種方案具有許多優點。假設查詢通常透過與相對較小的鍵值進行比較來限定,則執行器的大部分工作將使用主行條目完成。TOASTTOASTed 屬性的大值僅在結果集傳送到客戶端時才提取出來(如果被選中)。因此,主表要小得多,並且其更多行可以容納在共享緩衝區快取中,這在沒有行外儲存的情況下是不可能的。排序集也縮小,並且排序將更經常完全在記憶體中完成。一項小測試表明,包含典型 HTML 頁面及其 URL 的表以大約一半的原始資料大小儲存,包括 TOASTTOAST表,並且主表僅包含整個資料的大約 10%(URL 和一些小型 HTML 頁面)。與未 TOASTTOASTed 比較表相比,沒有執行時差異,在該表中,所有 HTML 頁面都被截斷為 7 kB 以適應。

66.2.2. 行外、記憶體中的 TOAST 儲存 #

TOASTTOAST 指標可以指向不在磁碟上但在當前伺服器程序記憶體中其他位置的資料。此類指標顯然不能是長壽命的,但它們仍然很有用。目前有兩種子情況:指向 間接 資料和指向 擴充套件 資料的指標。

間接 TOASTTOAST指標僅指向儲存在記憶體中某個位置的非間接 varlena 值。這種情況最初只是作為一個概念驗證而建立的,但目前它在邏輯解碼期間用於避免可能建立超過 1 GB 的物理元組(因為將所有行外欄位值拉入元組可能會這樣做)。這種情況的用途有限,因為指標資料項的建立者完全負責所引用資料在其存在期間的存活,並且沒有基礎設施來幫助實現這一點。

擴充套件 TOASTTOAST指標對於複雜的 資料型別很有用,這些資料型別的磁碟表示不特別適合計算目的。例如,PostgreSQL 陣列的標準 varlena 表示包括維度資訊、一個空值點陣圖(如果有任何空元素),然後是所有元素的按順序排列的值。當元素型別本身是變長時,找到第 N 個元素的唯一方法是掃描所有前面的元素。這種表示適用於磁碟儲存,因為它緊湊,但對於陣列的計算來說,擁有一個“擴充套件”或“解構”的表示要好得多,其中所有元素的起始位置都已確定。TOASTTOAST指標機制透過允許透過引用傳遞的 Datum 指向標準 varlena 值(磁碟表示)或 TOASTTOAST指標(指向記憶體中某個位置的擴充套件表示)來支援此需求。此擴充套件表示的詳細資訊取決於資料型別,但它必須具有標準頭並滿足 src/include/utils/expandeddatum.h 中給出的其他 API 要求。使用資料型別的 C 級函式可以選擇處理任一表示。不瞭解擴充套件表示但僅對其輸入應用 PG_DETOAST_DATUM 的函式將自動接收傳統的 varlena 表示;因此,可以逐步引入對擴充套件表示的支援,一次一個函式。

TOAST指向擴充套件值的 TOAST 指標進一步細分為 讀寫 指標和 只讀 指標。所指向的表示方式相同,但接收讀寫指標的函式被允許就地修改引用值,而接收只讀指標的函式則不允許;如果它想製作一個修改後的值版本,它必須首先建立一個副本。這種區別和一些相關約定使得在查詢執行期間可以避免不必要的擴充套件值複製。

對於所有型別的記憶體中 TOASTTOAST指標,TOASTTOAST管理程式碼確保任何此類指標資料項都不會意外地儲存在磁碟上。記憶體中 TOASTTOAST指標在儲存之前會自動擴充套件為正常的行內 varlena 值——然後可能轉換為磁碟上 TOASTTOAST指標,如果包含的元組否則會太大。

提交更正

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