https://cloud.tencent.com/developer/article/1004455
什么是TOAST技术
TOAST是The Oversized-Attribute Storage Technique的缩写,即行外存储,主要用于存储大字段的值。由于PostgreSQL块的大小是固定的(通常是8KB),且不允许行跨越多个页面,因此不可能存储非常大的字段值。为了突破这个限制,大的字段值通常被压缩或者切片成多个物理行存储到另一张系统表中,即TOAST表。只有特定的数据类型支持TOAST技术,整数、浮点等不太长的数据类型没必要使用TOAST技术。另外,支持TOAST技术的数据类型必须是变长的。
长度字
在变长类型中前4个字节(32bit)称为长度字,长度字后面存储的是具体内容或指针。
长度字的高2bit是标志位,后面的30位是长度值。由此可见TOAST数据类型的逻辑长度最多是30bit,即1GB之内(2^30 - 1)。
前2bit一个表示是否压缩,一个表示是否行外存储,如果两个都是0则表示该字段值既未被压缩也没有行外存储。如果设置了第一位则表示该数值被压缩过,使用前必须解压。如果设置了另外一位,则表示使用的是行外存储。此时长度字后面的部分只是一个指针,指向存储实际数据的TOAST表的位置。如果两个都设置了,那么这个行外数据也被压缩过了。不管哪种情况,长度字里剩下的30bit的长度值都表示数据的实际尺寸,而不是压缩过的数据长度。
只有当数据长度超过2040字节(大约一个块的四分之一)时才会触发压缩。在PostgreSQL 11之后可以通过toast_tuple_target参数来改变这个值的大小
alter table test01 set(toast_tuple_target=128);
如果没有使用行外存储则长度字后面的内容则是该字段具体的值
TOAST 的策略
- PLAIN :避免压缩和行外存储。只有那些不需要 TOAST 策略就能存放的数据类型允许选择(例如 int 类型),而对于 text 这类要求存储长度超过页大小的类型,是不允许采用此策略的
- EXTENDED :允许压缩和行外存储。一般会先压缩,如果还是太大,就会行外存储
- EXTERNA :允许行外存储,但不许压缩。类似字符串这种会对数据的一部分进行操作的字段,采用此策略可能获得更高的性能,因为不需要读取出整行数据再解压。
- MAIN :允许压缩,但不许行外存储。不过实际上,为了保证过大数据的存储,行外存储在其它方式(例如压缩)都无法满足需求的情况下,作为最后手段还是会被启动。因此理解为:尽量不使用行外存储更贴切。
例子
创建一张 blog 表
testdb=# create table blog(id int, title text, content text);
CREATE TABLE
testdb=# \d+ blog ;
Table "public.blog"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
---------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
id | integer | | | | plain | | |
title | text | | | | extended | | |
content | text | | | | extended | | |
Access method: heap
可以看到,interger 默认 TOAST 策略为 plain ,而 text 为 extended 。PG 资料告诉我们,如果表中有字段需要 TOAST ,
那么系统会自动创建一张 TOAST 表负责行外存储,那么这张表在哪里?
testdb=# select relname,relfilenode,reltoastrelid from pg_class where relname='blog';
relname | relfilenode | reltoastrelid
---------+-------------+---------------
blog | 16432 | 16435
(1 row)
通过上面语句,我们查到 blog 表的 oid 为16432,其对应 TOAST 表的 oid 为16435,
那么其对应 TOAST 表名则为: pg_toast.pg_toast_16432(注意这里是 blog 表的 oid ),我们看下其定义:
testdb=# \d+ pg_toast.pg_toast_16432;
TOAST table "pg_toast.pg_toast_16432"
Column | Type | Storage
------------+---------+---------
chunk_id | oid | plain
chunk_seq | integer | plain
chunk_data | bytea | plain
Owning table: "public.blog"
Indexes:
"pg_toast_16432_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
Access method: heap
TOAST 表有3个字段:
chunk_id :用来表示特定 TOAST 值的 OID ,可以理解为具有同样 chunk_id 值的所有行组成原表(这里的 blog )的 TOAST 字段的一行数据
chunk_seq :用来表示该行数据在整个数据中的位置
chunk_data :实际存储的数据。
插入数据
testdb=# insert into blog values(1, 'title', '0123456789');
INSERT 0 1
testdb=# select * from blog;
id | title | content
----+-------+------------
1 | title | 0123456789
(1 row)
--- 此时toast表中没有数据
testdb=# select * from pg_toast.pg_toast_16432;
chunk_id | chunk_seq | chunk_data
----------+-----------+------------
(0 rows)
testdb=# update blog set content=content||content where id=1;
UPDATE 1
--- 反复执行如上过程,直到 pg_toast_16441 表中有数据:
testdb=# select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_16432;
chunk_id | chunk_seq | length
----------+-----------+--------
16439 | 0 | 1996
16439 | 1 | 1996
16439 | 2 | 1996
16439 | 3 | 1996
16439 | 4 | 1996
16439 | 5 | 1996
16439 | 6 | 1996
16439 | 7 | 1050
(8 rows)
可以看到,直到 content 的长度为327680时(已远远超过页大小 8K),对应 TOAST 表中才有了2行数据,且长度都是略小于2K,这是因为 extended 策略下,先启用了压缩,然后才使用行外存储。
下面我们将 content 的 TOAST 策略改为 EXTERNA ,以禁止压缩。
标签:TOAST,toast,存储,chunk,技术,行外,pg From: https://www.cnblogs.com/gavin-zheng/p/18370355