摘要:介绍postgreSQL中的TOAST技术
TOAST(The Oversize-Attribute Storage Technique)技术是PG提供的一种存储大数据的机制。
要理解TOAST,我们要先理解页(BLOCK)的概念。在PG中,页是数据在文件存储中的基本单位,其大小是固定的且只能在编译期指定,之后无法修改,默认的大小为8KB。同时,PG不允许一行数据跨页存储。那么对于超长的行数据,PG就会启动TOAST,将大的字段压缩或切片成多个物理行存到另一张系统表中(TOAST表),这种存储方式叫行外存储。
在PostgreSQL中只有具有变长行为的数据类型(代码内部常称为varlena类型)才支持TOAST,比如TEXT数据类型。在向支持TOAST的属性中存储超过BLCKSZ/4字节(通常是2K)的数据时,TOAST机制才会被触发。在变长数据类型中,数值的前两位表示数值的存储方式:
- 如果是00,该数值是普通的未TOAST的数值。
- 如果是01,表示该数据被压缩过,剩下30位表示压缩后的数据大小,在这4字节之后还会附加4字节,表示未压缩的数据大小。
- 如果是1x,数据头部仅用一字节。当存储的是短字符串(小于128字节),剩下7位表示字符串长度。当该字节为10000000时,表明这是线外存储的数据,紧随其后使用1字节记录指针大小,数据区域记录TOAST指针。
注意:线外存储时,也可能进行压缩,这种情况通过TOAST指针中的va_rawsize和va_extsize来进行比较。
线外存储的数据会保存在称为TOAST表的普通表中。如果一个表中有一个属性是可TOAST的,那么该表将会有一个可关联的TOAST表,其OID存储在表的基本信息(也就是pg_class中的元组)的reltoastrelid属性中。如果没有关联的TOAST表,reltoastrelid属性为0。
TOAST机制有四种不同的存储策略(表中的Storage属性):
- PLAN:避免压缩或者线外存储。不支持toast的数据类型
- EXTENDED:允许压缩和线外存储,大多数可TOAST的数据类型的缺省值。
- EXTERNAL:允许线外存储但是不允许压缩。可以使text和bytea字段上的字串操作更快。
- MAIN:允许压缩,但是不允许线外存储。实际上这样的数据类型仍然可以进行线外存储。
存储策略可以使用ALTER TABLE SET STORAGE语句修改。
db=> create table t1 (c1 int, c2 text, c3 bytea);
CREATE TABLE
db=> \d+ t1Table "public.t1"Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------c1 | integer | | | | plain | | | c2 | text | | | | extended | | | c3 | bytea | | | | extended | | |
Access method: heap
db=> select relname, reltoastrelid from pg_class where relname = 't1';relname | reltoastrelid
---------+---------------t1 | 32785
(1 row)db=> select relname, relnamespace from pg_class where oid = '32785';relname | relnamespace
----------------+--------------pg_toast_32782 | 99
(1 row)
-- pg_toast_32777的命名空间
db=> select nspname from pg_namespace where oid = 99;
-[ RECORD 1 ]-----
nspname | pg_toast--TOAST表的定义
db=> \d+ pg_toast.pg_toast_32777
TOAST table "pg_toast.pg_toast_32777"Column | Type | Storage
------------+---------+---------chunk_id | oid | plainchunk_seq | integer | plainchunk_data | bytea | plain
Owning table: "public.t1"
Indexes:"pg_toast_32777_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
Access method: heap
TOAST表有三个字段:
- chunk_id :用来表示特定 TOAST 值的 OID ,可以理解为具有同样 chunk_id 值的所有行组成原表的 TOAST 字段的一行数据。
- chunk_seq: 用来表示该行数据在整个数据中的位置。
- chunk_data : 该Chunk实际的数据。
对应的TOAST表的数据结构:
*/
typedef struct varatt_external
{int32 va_rawsize; /* Original data size (includes header) */uint32 va_extinfo; /* External saved size (without header) and* compression method */Oid va_valueid; /* Unique ID of value within TOAST table */Oid va_toastrelid; /* RelID of TOAST table containing it */
} varatt_external;
探究TOAST机制
c2只有10个字符,所以没有压缩,也没有行外存储。然后我们使用如下 SQL 语句增加 c2的长度,每次增长1倍,同时观察 c2的长度。
db=> insert into t1 values(1, 'title', '0123456789');
INSERT 0 1
db=> select * from t1;c1 | c2 | c3
----+-------+------------------------1 | title | \x30313233343536373839
(1 row)db=> select * from pg_toast.pg_toast_32782;chunk_id | chunk_seq | chunk_data
----------+-----------+------------
(0 rows)
反复执行如上过程,直到 pg_toast_32782表中有数据。直到 content 的长度为327680时(已远远超过页大小 8K),对应 TOAST 表中才有了数据,且长度都是略小于2K,这是因为 extended 策略下,先启用了压缩,然后才使用行外存储。
db=> update t1 set c2=c2||c2 where c1=1;
UPDATE 1
.....db=> select c1, length(c2) from t1;c1 | length
----+----------1 | 83886080
(1 row)db=> select * from pg_toast.pg_toast_32782;
-[ RECORD 1 ]-----------------------------------------------------------------------------------------------
chunk_id | 32795
chunk_seq | 0
chunk_data | .....
-[ RECORD 2 ]-----------------------------------------------------------------------------------------------
chunk_id | 32795
chunk_seq | 1
chunk_data | .....
-[ RECORD 3 ]-----------------------------------------------------------------------------------------------
chunk_id | 32795
chunk_seq | 2
chunk_data | .....db=> select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_32782;chunk_id | chunk_seq | length
----------+-----------+--------32795 | 0 | 199632795 | 1 | 199632795 | 2 | 199632795 | 3 | 199632795 | 4 | 199632795 | 5 | 199632795 | 6 | 199632795 | 7 | 199632795 | 8 | 199632795 | 9 | 199632795 | 10 | 199632795 | 11 | 199632795 | 12 | 1996
参考:https://zhuanlan.zhihu.com/p/142281841