postgresql修炼之道_PostgreSQL的TOAST技术
本文參考:
- PostgreSQL TOAST 技術(shù)理解
- 《PostgreSQL修煉之道》
一、TOAST是什么?
TOAST是“The Oversized-Attribute Storage Technique”(超尺寸屬性存儲(chǔ)技術(shù))的縮寫,主要用于存儲(chǔ)一個(gè)大字段的值。
要理解TOAST,我們要先理解頁(yè)(BLOCK)的概念。在PG中,頁(yè)是數(shù)據(jù)在文件存儲(chǔ)中的基本單位,其大小是固定的且只能在編譯期指定,之后無法修改,默認(rèn)的大小為8KB。同時(shí),PG不允許一行數(shù)據(jù)跨頁(yè)存儲(chǔ)。那么對(duì)于超長(zhǎng)的行數(shù)據(jù),PG就會(huì)啟動(dòng)TOAST,將大的字段壓縮或切片成多個(gè)物理行存到另一張系統(tǒng)表中(TOAST表),這種存儲(chǔ)方式叫行外存儲(chǔ)。
二、使用TOAST
只有特定的數(shù)據(jù)類型支持TOAST,因?yàn)槟切┱麛?shù)、浮點(diǎn)數(shù)等不太長(zhǎng)的數(shù)據(jù)類型是沒有必要使用TOAST的。
另外,支持TOAST的數(shù)據(jù)類型必須是變長(zhǎng)的。在變長(zhǎng)類型中:
- 前4字節(jié)(32bit)稱為長(zhǎng)度字,長(zhǎng)度字后面存儲(chǔ)具體的內(nèi)容或一個(gè)指針。
- 長(zhǎng)度字的高2bit位是標(biāo)志位,后面的30bit是長(zhǎng)度值(表示值的總長(zhǎng)度,包括長(zhǎng)度字本身,以字節(jié)計(jì))。
- 由長(zhǎng)度值可知TOAST數(shù)據(jù)類型的邏輯長(zhǎng)度最多是30bit,即1GB(2^30-1字節(jié))之內(nèi)。
- 前2bit的標(biāo)志位,一個(gè)表示壓縮標(biāo)志位,一個(gè)表示是否行外存儲(chǔ),如果兩個(gè)都是零,那么表示既未壓縮也未行外存儲(chǔ)。
- 如果設(shè)置了壓縮標(biāo)志標(biāo)志位,表示該數(shù)值被壓縮過(使用的是非常簡(jiǎn)單且快速的LZ壓縮方法),使用前必須先解壓縮。
- 如果設(shè)置了行外存儲(chǔ)標(biāo)志位,則表示該數(shù)值是在行外存儲(chǔ)的。此時(shí),長(zhǎng)度字后面的部分只是一個(gè)指針,指向存儲(chǔ)實(shí)際數(shù)據(jù)的TOAST表中的位置。如果兩個(gè)標(biāo)志位都設(shè)置了,那么這個(gè)行外數(shù)據(jù)也會(huì)被壓縮。不管是哪種情況,長(zhǎng)度字里剩下的30bit的長(zhǎng)度值都表示數(shù)據(jù)的實(shí)際尺寸,而不是壓縮后的長(zhǎng)度。
在 PG 中每個(gè)表字段有四種 TOAST 的策略:
- PLAIN —— 避免壓縮和行外存儲(chǔ)。只有那些不需要 TOAST 策略就能存放的數(shù)據(jù)類型允許選擇(例如 int 類型),而對(duì)于 text 這類要求存儲(chǔ)長(zhǎng)度超過頁(yè)大小的類型,是不允許采用此策略的。
- EXTENDED —— 允許壓縮和行外存儲(chǔ)。一般會(huì)先壓縮,如果還是太大,就會(huì)行外存儲(chǔ)。這是大多數(shù)可以TOAST的數(shù)據(jù)類型的默認(rèn)策略。
- EXTERNAL —— 允許行外存儲(chǔ),但不許壓縮。這讓在text類型和bytea類型字段上的子串操作更快。類似字符串這種會(huì)對(duì)數(shù)據(jù)的一部分進(jìn)行操作的字段,采用此策略可能獲得更高的性能,因?yàn)椴恍枰x取出整行數(shù)據(jù)再解壓。
- MAIN —— 允許壓縮,但不許行外存儲(chǔ)。不過實(shí)際上,為了保證過大數(shù)據(jù)的存儲(chǔ),行外存儲(chǔ)在其它方式(例如壓縮)都無法滿足需求的情況下,作為最后手段還是會(huì)被啟動(dòng)。因此理解為:盡量不使用行外存儲(chǔ)更貼切。
首先創(chuàng)建一張 blog 表,查看它的各字段的TOAST策略:
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 默認(rèn) TOAST 策略為 PLAIN ,而 text 為 EXTENDED 。
另外可以修改某個(gè)字段系統(tǒng)默認(rèn)分配的TOAST策略,假如要將上面blog表中的content字段的TOAST策略改成EXTERNAL,就可以這樣:
ALTER TABLE blog ALTER content SET STORAGE EXTERNAL;三、TOAST表的結(jié)構(gòu)
如果一個(gè)表中有任何一個(gè)字段是可以TOAST的,那么PostgreSQL會(huì)自動(dòng)為該表建一個(gè)相關(guān)聯(lián)的TOAST表,其OID存儲(chǔ)在pg_class系統(tǒng)表的reltoastrelid記錄里,行外的內(nèi)容保存在TOAST表里。
查看blog表對(duì)應(yīng)的TOAST表的OID:
postgres=# select relname,relfilenode,reltoastrelid from pg_class where relname='blog';relname | relfilenode | reltoastrelid ---------+-------------+---------------blog | 16441 | 16444 (1 row)通過上述語句,我們查到 blog 表的 oid 為16441,其對(duì)應(yīng) TOAST 表的 oid 為16444(關(guān)于 oid 和 pg_class 的概念,請(qǐng)參考PG官方文檔),那么其對(duì)應(yīng) TOAST 表名則為: pg_toast.pg_toast_16441(注意這里是 blog 表的 oid )。
行外存儲(chǔ)被切成了多個(gè)Chunk塊,每個(gè)Chunk塊大約是一個(gè)BLOCK的四分之一大小,如果塊大小為8KB(默認(rèn)就是8KB),則Chunk大約為2KB(比2KB略小一點(diǎn)),每個(gè)Chunk都作為獨(dú)立的行存儲(chǔ)在TOAST表中。
TOAST表有三個(gè)字段:
- chunk_id —— 用來表示特定 TOAST 值的 OID ,可以理解為具有同樣 chunk_id 值的所有行組成原表(這里的 blog )的 TOAST 字段的一行數(shù)據(jù)。
- chunk_seq —— 用來表示該行數(shù)據(jù)在整個(gè)數(shù)據(jù)中的位置。
- chunk_data —— 該Chunk實(shí)際的數(shù)據(jù)。
我們看下上面的TOAST表pg_toast.pg_toast_16441的定義:
postgres=# d+ pg_toast.pg_toast_16441; TOAST table "pg_toast.pg_toast_16441"Column | Type | Storage ------------+---------+---------chunk_id | oid | plainchunk_seq | integer | plainchunk_data | bytea | plain在chunk_id和chunk_seq上有一個(gè)唯一的索引,提供對(duì)數(shù)值的快速檢索。
因此,一個(gè)表示行外存儲(chǔ)的指針數(shù)據(jù)中包括了要查詢的TOAST表的OID和特定數(shù)值的chunk_id(也是一個(gè)OID類型)。為了方便,指針數(shù)據(jù)還存儲(chǔ)了邏輯數(shù)據(jù)的尺寸(原始的未壓縮的數(shù)據(jù)長(zhǎng)度)及實(shí)際存儲(chǔ)的尺寸(如果使用了壓縮,則兩者不同)。加上頭部的長(zhǎng)度字,一個(gè)TOAST指針數(shù)據(jù)的總尺寸是20字節(jié)。
四、TOAST技術(shù)實(shí)踐
現(xiàn)在我們來實(shí)際驗(yàn)證下TOAST:
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)可以看到因?yàn)?content 只有10個(gè)字符,所以沒有壓縮,也沒有行外存儲(chǔ)。然后我們使用如下 SQL 語句增加 content 的長(zhǎng)度,每次增長(zhǎng)1倍,同時(shí)觀察 content 的長(zhǎng)度,看看會(huì)發(fā)生什么情況?
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)反復(fù)執(zhí)行如上過程,直到 pg_toast_16441 表中有數(shù)據(jù):
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 | 199616439 | 1 | 1773 (2 rows)可以看到,直到 content 的長(zhǎng)度為327680時(shí)(已遠(yuǎn)遠(yuǎn)超過頁(yè)大小 8K),對(duì)應(yīng) TOAST 表中才有了2行 數(shù)據(jù),且長(zhǎng)度都是略小于2K,這是因?yàn)?extended 策略下,先啟用了壓縮,然后才使用行外存儲(chǔ)。
下面我們將 content 的 TOAST 策略改為 EXTERNAL ,以禁止壓縮。
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 | |然后我們?cè)俨迦胍粭l數(shù)據(jù):
postgres=# insert into blog values(2, 'title', '0123456789'); INSERT 0 1 postgres=# select id,title,length(content) from blog;id | title | length ----+-------+--------1 | title | 3276802 | title | 10 (2 rows)然后重復(fù)以上步驟,直到 pg_toast_16441 表中有數(shù)據(jù):
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 | 3276801 | 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 | 199616447 | 1 | 177316448 | 0 | 199616448 | 1 | 199616448 | 2 | 1996....(省略)16448 | 164 | 1996 (167 rows)因?yàn)椴辉试S壓縮,所以新的操作在TOAST表中生成了更多Chunk塊行記錄。通過以上操作得出以下結(jié)論:
- 如果策略允許壓縮,則TOAST優(yōu)先選擇壓縮。
- 不管是否壓縮,一旦數(shù)據(jù)超過2KB左右,就會(huì)啟用行外存儲(chǔ)。
- 修改TOAST策略,不會(huì)影響現(xiàn)有數(shù)據(jù)的存儲(chǔ)方式。
五、TOAST技術(shù)總結(jié)
TOAST比那些更直接的方法(比如允許行值跨越多個(gè)頁(yè)面)有更多優(yōu)點(diǎn)。 假設(shè)查詢通常是用相對(duì)比較短的鍵值進(jìn)行匹配的,那么執(zhí)行器的大多數(shù)工作都將使用主行項(xiàng)完成。TOAST過的屬性的大值只是在把結(jié)果集發(fā)送給客戶端的時(shí)候才被抽出來(如果它被選中)。 因此,主表要小得多,并且它的能放入到共享緩沖區(qū)中的行要比沒有任何行外存儲(chǔ)的方案更多。 排序集也縮小了,并且排序?qū)⒏嗟卦趦?nèi)存里完成。一個(gè)小測(cè)試表明,一個(gè)典型的保存 HTML 頁(yè)面以及它們的 URL 的表占用的存儲(chǔ)(包括TOAST表在內(nèi))大約只有裸數(shù)據(jù)的一半,而主表只包含全部數(shù)據(jù)的 10%(URL和一些小的 HTML 頁(yè)面)。與在一個(gè)非TOAST的對(duì)照表里面存儲(chǔ)(把全部 HTML 頁(yè)面裁剪成 7Kb 以匹配頁(yè)面大小)同樣的數(shù)據(jù)相比,運(yùn)行時(shí)沒有任何區(qū)別。
總結(jié)
以上是生活随笔為你收集整理的postgresql修炼之道_PostgreSQL的TOAST技术的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件需求分析文档模板_小议管理软件需求分
- 下一篇: mysql8导出文件_windows下