?
This document uses PHP Chinese website manual Release
本節(jié)提供一個TOAST的概述。(超大字段存儲技術)
因為PostgreSQL的頁面大小是固定的(通常是 8Kb), 并且不允許行跨越多個頁面,因此不可能直接存儲非常大的字段值。為了突破這個限制, 大的字段值被壓縮和/或打碎成多個物理行。這些事情對用戶都是透明的, 只是在后端代碼上有一些小的影響。這個技術稱為TOAST。 ("切片面包之后最好的東西")
只有一部分數(shù)據(jù)類型支持TOAST(沒必要在那些不可能生成大的字段值
的數(shù)據(jù)類型強制這種額外開銷)。要支持TOAST,數(shù)據(jù)類型必須有變長
(varlena)表現(xiàn)形式,這個時候,任何存儲的數(shù)值的頭 32 位都是
存儲著以字節(jié)記的數(shù)值的總長度(包括長度本身)。TOAST
并不約束剩下的表現(xiàn)形式。所有支持TOAST 的數(shù)據(jù)類型之
C 級別的函數(shù)都必須仔細處理TOAST 的輸入值。
也就是通常是在對一個輸入值做任何事情之前調(diào)用PG_DETOAST_DATUM
;
但是在某些情況下也存在更高效的方法。
TOAST 占用變長的長度字的兩位(在大型機器上高位序,在小型機器上低位序), 因此限制可TOAST數(shù)據(jù)類型任何值的邏輯大小為1 GB(230 - 1 字節(jié))。 當兩位都是零時,該值是一個普通的非TOAST數(shù)據(jù)類型的值,長度字的剩下位給總數(shù)據(jù)大小以字節(jié)計。(包括長度字) 當設置最高或最低位,該值僅有一個字節(jié)頭替代通常的4字節(jié)頭,而剩余的位給總數(shù)據(jù)大小以字節(jié)計。(包括長度字) 作為一個特殊的情況下,如果剩余位都是零(其將不可能包含自身的長度),該值為一個指向存儲在TOAST表的行外數(shù)據(jù)。 (一個TOAST指針的大小是給定的在第二個字節(jié)的數(shù)據(jù)。)單字節(jié)頭的值沒有對齊任何特定的邊界。最后當清除最高或最低位時, 但是設置了臨近的位,壓縮了數(shù)據(jù)內(nèi)容,在使用前必須解壓縮。在這種情況下長度字剩余位給壓縮數(shù)據(jù)的總大小,而不是原數(shù)據(jù)的。 請注意壓縮也可能是行外數(shù)據(jù),但是變長的頭不會告訴這是否發(fā)生—TOAST指針的內(nèi)容告訴這些,替代。
如果一個表中有任何一個字段是可以TOAST的,那么該表將有一 個關聯(lián)的TOAST表,其 OID 存儲在表的 pg_class.reltoastrelid記錄里, 行外TOAST過的數(shù)值保存在TOAST表里, 下面有更詳細的描述。
這里使用的壓縮技術是非常簡單并且非??焖俚?LZ 族壓縮技術。 參閱src/backend/utils/adt/pg_lzcompress.c獲取細節(jié)。
將外數(shù)據(jù)分割成(如果壓縮過,在壓縮之后)最多TOAST_MAX_CHUNK_SIZE (缺省選擇這個值, 2000字節(jié) ,使4塊行將適合一內(nèi)存頁,約2000個字節(jié))字節(jié),每個塊都作為獨立的行 在TOAST表里為所屬表存儲。每個TOAST表都有chunk_id 字段(一個表示特定TOAST值的 OID)、chunk_seq (一個序列號,存儲該塊在數(shù)值中的位置)、chunk_data(該塊實際的數(shù)據(jù))。 在chunk_id和chunk_seq上有一個唯一索引, 提供對數(shù)值的快速檢索。因此,一個表示行外TOAST值的指針數(shù)據(jù)需要 存儲要查閱的TOAST的 OID 和特定數(shù)值的 OID(它的chunk_id)。 為了方便,指針數(shù)據(jù)還存儲邏輯數(shù)據(jù)的尺寸(原始的未壓縮的數(shù)據(jù)長度)以及實際存儲的尺寸 (如果使用了壓縮,則兩者不同)。加上頭部的長度字,一個TOAST指針數(shù)據(jù)的總 尺寸是 20 字節(jié),不管它代表的數(shù)值的實際長度是多大。
TOAST代碼只有在準備向某表中存儲超過TOAST_TUPLE_THRESHOLD 字節(jié)(通常是 2KB)的行的時候才會觸發(fā)。TOAST代碼將壓縮和/或行外存儲字段值, 直到數(shù)值比TOAST_TUPLE_TARGET字節(jié)(通常是2KB)短,或者無法得到更好的結果 的時候才停止。在一個 UPDATE 操作過程中,未改變的字段值通常原樣保存; 所以,如果 UPDATE 一個帶有行外數(shù)據(jù)的行時,如果行外數(shù)據(jù)值沒有變化, 那么將不會有TOAST開銷存在。
TOAST代碼識別四種不同的存儲可TOAST字段的策略:
PLAIN避免壓縮或者行外的存儲;此外,它禁止使用單字節(jié)的頭變長類型。 這只是對那些不能 TOAST 的數(shù)據(jù)類型才有可能。
EXTENDED允許壓縮和行外存儲。這是大多數(shù)可 以TOAST的數(shù)據(jù)類型的缺省。首先將企圖進行壓縮, 如果行仍然太大,那么則進行行外存儲。
EXTERNAL允許行外存儲,但是不許壓縮。 使用EXTERNAL,將令那些在text和bytea字段上的子字符串操作更快 (代價是增加了存儲空間),因為這些操作是經(jīng)過優(yōu)化的:如果行外數(shù)據(jù)沒有壓縮, 那么它們只會去抓取需要的部分。
MAIN允許壓縮,但不允許行外存儲。 實際上,在這樣的字段上仍然會進行行外存儲,但只是作 為沒有辦法把數(shù)據(jù)行變得更小的情況下使之足以容納一個頁面的最后的手段。
每個可TOAST的數(shù)據(jù)類型都為該數(shù)據(jù)類型的字段指定一個缺省策略, 但是特定表的字段的存儲策略可以用ALTER TABLE SET STORAGE修改。
這個方法比那些更直接的方法,比如允許行值直接跨越多個頁面, 有更多優(yōu)點。假設查詢通常是用相對比較短的鍵值進行匹配的, 那么大多數(shù)執(zhí)行器的工作都將使用主行記錄完成。TOAST的屬性的大值, 只是在把結果集發(fā)送給客戶端的時候才抽出來(如果選擇了它的話)。因此, 主表要小得多,并且它的大部分行都存儲在共享緩沖區(qū)里,因此就可以不需要任何行外存儲。 排序集也縮小了,并且排序?qū)⒏嗟卦趦?nèi)存里完成。一個小測試表明,一個用于 保存 HTML 頁面以及它們的 URL 的表,包括TOAST表在內(nèi), 存儲將近一半大小的裸數(shù)據(jù),而主表只包含全部數(shù)據(jù)的 10%(URL 和一些小的 HTML 頁面)。 與在一個非TOAST的對比表里面存儲(把全部 HTML 頁面裁剪成 7KB 以匹配頁面大小), 沒有任何運行時的區(qū)別。