Postgresql TOAST
TOAST
(The Oversized-Attribute Storage Technique) 超尺寸字段存儲技術。就是說超長字段在Postgres的一個存儲方式。
WHY?
PostgreSQL page大小是固定的(通常為8KB),且不允許tuples跨多個page存儲。是以不能存儲非常大的字段值。為了克服這個限制,大字段值需要壓縮甚至分割成多個實體行進行存儲,這就是TOAST技術。TOAST對使用者來說是透明的。
注:
Postgres的部分類型資料支援toast,這是因為有些字段類型是不會産生大字段資料的,完全沒必要用到Toast技術(比如date,time,boolean等)。
存儲方式
Out-of-line, on-disk TOAST storage 行外磁盤存儲
- 當表中字段任何一個有Toast,那這個表都會有這一個相關聯的Toast表,OID被存儲在pg_class.reltoastrelid裡面。Out-of-line values(可能是壓縮後的,如果使用了壓縮)将會被分割成chunks,每個chunk大小為toast_max_chunk_size(預設是2Kb),每個chunk作為單獨的一行存儲在TOAST表中。
- 相比較普通表(MAIN TABLE),TOAST有額外的三個字段(chunk_id,chunk_seq,chunk_data),有唯一索引在chunk_id和hunk_seq上提供快速查詢。
chunk_id :辨別TOASTed值的OID字段
chunk_seq :chunk的序列号,與chunk_id的組合唯一索引可以加速通路
chunk_data :存儲TOAST的實際資料
- 當存儲的行資料超過toast_tuple_threshold值(通常是2kB),就會觸發toast存儲,這時toast将會壓縮或者移動超出的字段值直到行資料比toast_tuple_targer值小(這個值通常也是2KB)。是以基礎表上可能隻存了20%的資料
Toast有識别4種不同可存儲toast的政策:
--plain避免壓縮或行外存儲
PLAIN prevents either compression or out-of-line storage; furthermore it disables use of single-byte headers for varlena types. This is the only possible strategy for columns of non-TOAST-able data types
--extended允許壓縮和行外存儲(預設toast存儲)
EXTENDED allows both compression and out-of-line storage. This is the default for most TOASTable data types. Compression will be attempted first, then out-of-line storage if the row is still too big
--external允許行外但不允許壓縮
EXTERNAL allows out-of-line storage but not compression. Use of EXTERNAL will make substring operations on wide text and bytea columns faster(at the penalty of increased storage space) because these operations are optimized to fetch only the required parts of the out-of-line value when it is not compressed
--main允許壓縮但不允許行外存儲
MAIN allows compression but not out-of-line storage. (Actually, out-of-line storage will still be performed for such columns, but only as a last resort when there is no other way to make the row small enough to fit on a page
上述壓縮采用的是LZ compression技術。
可以通過 ALTER TABLE ... SET STORAGE更改字段的存儲政策
事例
檢視TOAST存儲
CREATE TABLE test_toast(
id int,
name text,
age int,
create_time timestamp without time zone);
INSERT INTO test_toast SELECT generate_series(1,10000),md5(random()::text),
((random()*100)::integer),clock_timestamp();
postgres=> \d+ test_toast;
Table "test.test_toast"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
-------------+-----------------------------+-----------+----------+---------+----------+--------------+-------------
id | integer | | | | plain | |
name | text | | | | extended | |
age | integer | | | | plain | |
create_time | timestamp without time zone | | | | plain | |
postgres=>
postgres=> select relname,relfilenode,reltoastrelid from pg_class where relname='test_toast';
relname | relfilenode | reltoastrelid
------------+-------------+---------------
test_toast | 102417 | 102420
(1 row)
注意:TOAST表名,可通過以下方式檢視
postgres=> \! oid2name -d postgres -f 102420
From database "postgres":
Filenode Table Name
---------------------------
102420 pg_toast_102417
含有TOAST表的空間大小計算!
如果表中有某些字段使用TOAST進行存儲,那麼,通過普通的pg_relation_size('表名')查詢不到TOAST字段所占用的空間。如果要查詢TOAST字段所占用的空間,可以先查詢出TOAST字段對應的OID,再通過pg_relation_size(OID)的方式查詢出TOAST字段所占用的空間。
select pg_size_pretty(pg_relation_size('test_toast','main'));
select pg_size_pretty(pg_relation_size(102420));
增加字段大小,産生TOAST存儲
update test_toast set name=name||name where id=1;
postgres=> select pg_size_pretty(pg_relation_size(102417));
pg_size_pretty
----------------
832 kB
(1 row)
postgres=> select pg_size_pretty(pg_relation_size(102420));
pg_size_pretty
----------------
3072 kB
(1 row)
postgres=> select pg_size_pretty(pg_table_size('test_toast'));
pg_size_pretty
----------------
4016 kB
(1 row)
使用pg_table_size查出的結果是包括TOAST字段所占用的空間的。
注意:實體檔案空間大小查詢參考《Cluster database and table》
TOAST的優缺點
Toast的優點
- 可以存儲超長超大字段,避免之前不能直接存儲的限制
- 實體上與普通表是分離的,檢索查詢時不檢索到該字段會極大地加快速度
-
更新普通表時,該表的Toast資料沒有被更新時,不用去更新Toast表
Toast的劣勢:
- 對大字段的索引建立是一個問題,有可能會失敗,其實通常也不建議在大字段上建立,全文檢索倒是一個解決方案。
- 大字段的更新會有點慢,其它DB也存在,通病