騰訊雲數據庫團隊:PostgreSQL TOAST 技術理解

騰訊雲技術社區-掘金主頁持續爲你們呈現雲計算技術文章,歡迎你們關注!javascript


做者介紹:胡彬 騰訊雲高級工程師html

TOAST是「The Oversized-Attribute Storage Technique」的縮寫,主要用於存儲一個大字段的值。要理解TOAST,咱們要先理解頁(BLOCK)的概念。在PG中,頁是數據在文件存儲中的基本單位,其大小是固定的且只能在編譯期指定,以後沒法修改,默認的大小爲8KB。同時,PG不容許一行數據跨頁存儲,那麼對於超長的行數據,PG就會啓動TOAST,具體就是採用壓縮和切片的方式。若是啓用了切片,實際數據存儲在另外一張系統表的多個行中,這張表就叫TOAST表,這種存儲方式叫行外存儲。java

在深刻細節以前,咱們要先了解,在PG中每一個表字段有四種TOAST的策略:node

  • PLAIN:避免壓縮和行外存儲。只有那些不須要TOAST策略就能存放的數據類型容許選擇(例如int類型),而對於text這類要求存儲長度超過頁大小的類型,是不容許採用此策略的sql

  • EXTENDED:容許壓縮和行外存儲。通常會先壓縮,若是仍是太大,就會行外存儲post

  • 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表負責行外存儲,那麼這張表在哪裏?spa

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。經過以上操做得出如下結論:

此文已由做者受權騰訊雲技術社區發佈,轉載請註明文章出處
原文連接:www.qcloud.com/community/a…
獲取更多騰訊海量技術實踐乾貨,歡迎你們前每每騰訊雲技術社區

相關文章
相關標籤/搜索