postgreSQL中的TOAST技术

摘要:介绍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+ 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表有三个字段:

  • 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 |   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

你可能感兴趣的:(数据库,postgresql,数据库)