摘要:介绍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机制才会被触发。在变长数据类型中,数值的前两位表示数值的存储方式:
注意:线外存储时,也可能进行压缩,这种情况通过TOAST指针中的va_rawsize和va_extsize来进行比较。
线外存储的数据会保存在称为TOAST表的普通表中。如果一个表中有一个属性是可TOAST的,那么该表将会有一个可关联的TOAST表,其OID存储在表的基本信息(也就是pg_class中的元组)的reltoastrelid属性中。如果没有关联的TOAST表,reltoastrelid属性为0。
TOAST机制有四种不同的存储策略(表中的Storage属性):
存储策略可以使用ALTER TABLE SET STORAGE语句修改。
db=> create table t1 (c1 int, c2 text, c3 bytea);
CREATE TABLE
db=> \d+ t1
Table "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 | plain
chunk_seq | integer | plain
chunk_data | bytea | plain
Owning table: "public.t1"
Indexes:
"pg_toast_32777_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
Access method: heap
TOAST表有三个字段:
对应的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 | 1996
32795 | 1 | 1996
32795 | 2 | 1996
32795 | 3 | 1996
32795 | 4 | 1996
32795 | 5 | 1996
32795 | 6 | 1996
32795 | 7 | 1996
32795 | 8 | 1996
32795 | 9 | 1996
32795 | 10 | 1996
32795 | 11 | 1996
32795 | 12 | 1996
参考:https://zhuanlan.zhihu.com/p/142281841