作者介紹:胡彬 騰訊雲高級工程師
TOAST是“The Oversized-Attribute Storage Technique”的縮寫,主要用於存儲一個大字段的值。要理解TOAST,我們要先理解頁(BLOCK)的概念。在PG中,頁是數據在文件存儲中的基本單位,其大小是固定的且只能在編譯期指定,之后無法修改,默認的大小為8KB。同時,PG不允許一行數據跨頁存儲,那么對於超長的行數據,PG就會啟動TOAST,具體就是采用壓縮和切片的方式。如果啟用了切片,實際數據存儲在另一張系統表的多個行中,這張表就叫TOAST表,這種存儲方式叫行外存儲。
在深入細節之前,我們要先了解,在PG中每個表字段有四種TOAST的策略:
-
PLAIN:避免壓縮和行外存儲。只有那些不需要TOAST策略就能存放的數據類型允許選擇(例如int類型),而對於text這類要求存儲長度超過頁大小的類型,是不允許采用此策略的
-
EXTENDED:允許壓縮和行外存儲。一般會先壓縮,如果還是太大,就會行外存儲
-
EXTERNA:允許行外存儲,但不許壓縮。類似字符串這種會對數據的一部分進行操作的字段,采用此策略可能獲得更高的性能,因為不需要讀取出整行數據再解壓。
-
MAIN:允許壓縮,但不許行外存儲。不過實際上,為了保證過大數據的存儲,行外存儲在其它方式(例如壓縮)都無法滿足需求的情況下,作為最后手段還是會被啟動。因此理解為:盡量不使用行外存儲更貼切。 現在我們通過實際操作來研究TOAST的細節:
首先創建一張blog表:
postgres=# create table blog(id int, title text, content text); CREATE TABLE postgres=# \d+ blog; Table "public.blog" Column | Type | Modifiers | Storage | Stats target | Description ---------+---------+-----------+----------+--------------+------------- id | integer | | plain | | title | text | | extended | | content | text | | extended | |
可以看到,interger默認TOAST策略為plain,而text為extended。PG資料告訴我們,如果表中有字段需要TOAST,那么系統會自動創建一張TOAST表負責行外存儲,那么這張表在哪里?
postgres=# select relname,relfilenode,reltoastrelid from pg_class where relname='blog'; relname | relfilenode | reltoastrelid ---------+-------------+--------------- blog | 16441 | 16444 (1 row)
通過上訴語句,我們查到blog表的oid為16441,其對應TOAST表的oid為16444(關於oid和pg_class的概念,請參考PG官方文檔),那么其對應TOAST表名則為:pg_toast.pg_toast_16441(注意這里是blog表的oid),我們看下其定義:
postgres=# \d+ pg_toast.pg_toast_16441; TOAST table "pg_toast.pg_toast_16441" Column | Type | Storage ------------+---------+--------- chunk_id | oid | plain chunk_seq | integer | plain chunk_data | bytea | plain
TOAST表有3個字段:
-
chunk_id:用來表示特定TOAST值的OID,可以理解為具有同樣chunk_id值的所有行組成原表(這里的blog)的TOAST字段的一行數據
-
chunk_seq:用來表示該行數據在整個數據中的位置
-
chunk_data:實際存儲的數據。 現在我們來實際驗證下:
postgres=# insert into blog values(1, 'title', '0123456789'); INSERT 0 1 postgres=# select * from blog; id | title | content ----+-------+------------ 1 | title | 0123456789 (1 row) postgres=# select * from pg_toast.pg_toast_16441; chunk_id | chunk_seq | chunk_data ----------+-----------+------------ (0 rows)
可以看到因為content只有10個字符,所以沒有壓縮,也沒有行外存儲。然后我們使用如下SQL語句增加content的長度,每次增長1倍,同時觀察content的長度,看看會發生什么情況?
postgres=# update blog set content=content||content where id=1; UPDATE 1 postgres=# select id,title,length(content) from blog; id | title | length ----+-------+-------- 1 | title | 20 (1 row) postgres=# select * from pg_toast.pg_toast_16441; chunk_id | chunk_seq | chunk_data ----------+-----------+------------ (0 rows)
反復執行如上過程,直到pg_toast_16441表中有數據:
postgres=# select id,title,length(content) from blog; id | title | length ----+-------+-------- 1 | title | 327680 (1 row) postgres=# select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_16441; chunk_id | chunk_seq | length ----------+-----------+-------- 16439 | 0 | 1996 16439 | 1 | 1773 (2 rows)
可以看到,直到content的長度為327680時(已遠遠超過頁大小8K),對應TOAST表中才有了2行數據,且長度都是略小於2K,這是因為extended策略下,先啟用了壓縮,然后才使用行外存儲
下面我們將content的TOAST策略改為EXTERNA,以禁止壓縮。
postgres=# alter table blog alter content set storage external; ALTER TABLE postgres=# \d+ blog; Table "public.blog" Column | Type | Modifiers | Storage | Stats target | Description ---------+---------+-----------+----------+--------------+------------- id | integer | | plain | | title | text | | extended | | content | text | | external | |
然后我們再插入一條數據:
postgres=# insert into blog values(2, 'title', '0123456789'); INSERT 0 1 postgres=# select id,title,length(content) from blog; id | title | length ----+-------+-------- 1 | title | 327680 2 | title | 10 (2 rows)
然后重復以上步驟,直到TOAST表中產生新的行:
postgres=# update blog set content=content||content where id=2; UPDATE 1 postgres=# select id,title,length(content) from blog; id | title | length ----+-------+-------- 2 | title | 2560 1 | title | 327680 (2 rows) postgres=# select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_16441; chunk_id | chunk_seq | length ----------+-----------+-------- 16447 | 0 | 1996 16447 | 1 | 1773 16448 | 0 | 1996 16448 | 1 | 564 (4 rows)
這次我們看到當content長度達到2560(按照官方文檔,應該是超過2KB左右),TOAST表中產生了新的2條chunk_id為16448的行,且2行數據的chunk_data的長度之和正好等於2560。通過以上操作得出以下結論:
- 如果策略允許壓縮,則TOAST優先選擇壓縮
- 不管是否壓縮,一旦數據超過2KB左右,就會啟用行外存儲
- 修改TOAST策略,不會影響現有數據的存儲方式
相關閱讀:
此文已由作者授權騰訊雲技術社區發布,轉載請注明文章出處,原文鏈接https://www.qcloud.com/community/article/164816001481011925
獲取更多雲計算技術干貨,可請前往騰訊雲技術社區,歡迎大家關注騰訊雲技術社區-博客園官方主頁,我們將持續在博客園為大家推薦技術精品文章哦~
