49.2. TOAST

本節提供 TOAST 的一個概述(超尺寸字串儲存技巧-The Oversized-Attribute Storage Technique)。

因為 PostgreSQL 的頁面大小是固定的(通常是8Kb), 並且不允許遠祖跨越多個頁面,因此不可能直接儲存非常大的字串值。 在 PostgreSQL 7.1 之前,代碼裡有一個硬限制, 限制了一個資料表中一個資料行儲存的資料的總大小為剛好略小於一個頁面。 從版本 7.1 以及以後的版本開始,這個限制被克服了,方法是允許大的字串值被壓縮和/或打碎成多個實際行。 這些事情對用戶都是透明的,只是在後端代碼上有一些小的影響。 這個技術的愛稱是TOAST(或者"切片麵包之後的最好的東西")。

只有一部分資料類型支援 TOAST — 我們沒必要在那些不可能生成大的字串值的資料類型強制這種過熱。 要支援 TOAST,資料類型必須有變長(varlena)的資料表現形式, 這個時候,任何儲存的數值的頭 32 位都是儲存著以字元記的數值的總長度(包括長度本身)。 TOAST 並不約束剩下的資料表現形式。所有支援可以 TOAST 的資料類型之 C 級別的函數都必須仔細處理 TOAST 的輸入值。(通常是在對一個輸入值做任何事情之前,調用PG_DETOAST_DATUM; 但是在某些情況下,更高效的方法也是存在的。)

TOAST 使用變長的長度字的最高兩個二進制位, 這樣就把任何可以TOAST的資料類型的邏輯長度限制在1Gb(230 - 1 字元)。 如果兩個位都是零,那麼數值是該資料類型一個普通的未TOAST的數值。 如果其中一個位置了一,那麼資料表示該數值被壓縮過,使用前必須先解壓縮。如果設置了另外一個位, 則資料表示該數值是在線外儲存的。這個時候,該值剩下的部分只是一個指針, 而正確的數值必須在其他地方查找。如果兩個位都設置上了, 那麼這個線外資料也被壓縮過了。不管哪種情況,長度字裡剩下的低位都資料表示資料的實際尺寸, 而不是解壓縮或者從線外資料抓過來之後的邏輯尺寸。

如果一個資料表中有任何一個字串是可以TOAST的, 那麼該資料表將有一個關聯的TOAST資料表,其 OID 儲存在資料表的pg_class.reltoastrelid 記錄力,線外TOAST過的數值保存在TOAST資料表裡,下面有更詳細的描述。

這裡使用的壓縮技術是非常簡單並且非常快速的 LZ 族壓縮技巧。 參閱 src/backend/utils/adt/pg_lzcompress.c 獲取細節。

線外資料被分裂成(如果壓縮過,在壓縮之後)最多TOAST_MAX_CHUNK_SIZE (這個數值略小於BLCKSZ/4,或者預設 2000 字元)字元的塊, 每個塊都作為獨立的行在TOAST資料表裡為所屬資料表儲存。 每個TOAST資料表都有字串chunk_id(一個資料表示特定的TOAST過之資料的 OID), chunk_seq(一個序列號,儲存該塊在數值中的位置),和一個chunk_data (該塊實際的資料)。在chunk_idchunk_seq上有一個唯一索引, 提供對數值的快速檢索。因此,一個資料表示線外TOAST過的數值的指針資料需要儲存要查閱的TOAST的OID 和特定數值的OID(它的chunk_id)。為了方便, 指針資料還儲存邏輯資料的尺寸(原始的未壓縮的資料長度)以及實際儲存的尺寸(如果使用了壓縮,則兩者不同)。 加上頭部的長度字,一個TOAST指針資料的總尺寸是20字元,不管它代資料表的數值的實際長度是多大。

TOAST 代碼只有在準備向某資料表中儲存超過BLCKSZ/4字元(通常是2Kb)的行的時候才會觸發。 TOAST 代碼將壓縮和/或線外儲存字串值,直到數值比BLCKSZ/4字元短,或者無法得到更好的結果的時候才停止。 在一個 UPDATE 操作過程中,未改變的字串的數值通常原樣保存; 所以,如果 UPDATE 一個帶有線外資料的行時,假如線外資料值沒有變化,那麼將不會有TOAST開銷存在。

TOAST代碼識別四種不同的儲存可TOAST字串的策略:

每個可以 TOAST 的資料類型都為該資料類型的字串聲明一個預設策略, 但是特定資料表的字串的儲存策略可以用 ALTER TABLE SET STORAGE 修改。

這個方法比那些更直接的方法,比如允許行數值直接跨越多個頁面,有更多優點。 假設查詢通常是用相對比較短的鍵值進行匹配的,那麼大多數執行器的工作都將使用主行記錄完成。 TOAST 過的屬性的大的數值只是在把結果集發送給客戶端的時候才抽出來(如果選擇了它的話)。 因此,主資料表要小得多,並且它的大部分行都儲存在共享緩衝區力,因此就可以不需要任何線外儲存。 排序集也縮小了,並且排序將更多地在內存裡完成。一個小測試資料表明,一個典型的保存 HTML 頁面以及它們的 URL 的資料表將在包括 TOAST 資料表在內內儲存將近一半大小的裸資料, 而主資料表只包含全部資料的 10%(URL和一些小的 HTML 頁面)。 與在一個非 TOAST 的伴侶資料表裡面儲存(把全部 HTML 頁面裁剪成 7Kb 以匹配頁面大小),沒有任何執行時的區別。