PostgreSQL数据库不允许元组(行,记录)跨越多个页面(page)存储,所以,它不能直接存储非常大的字段值。对于大字段值,它将被压缩且(或)分解为多个物理行,该技术称为“TOAST”。

toast技术特点

  1. toast是超长字段在PG的一个存储方式;
  2. 全称The overSized Attribute Stroage Technique(超尺寸字段存储技术);
  3. 它会将大字段值压缩或者分散为多个物理行来存储;
  4. 对于用户来说不用关注这一技术实现,完全是透明的;

toast的存储方式

  • pg的部分类型数据支持toast,因为有些字段类型是不会产生大字段数据(比如date,time,boolean);
  • 支持toast的数据类型应当是可变长度的(variable-length);
  • 表中任何一个字段有toast,这个表都会有一个相关联的toast表,oid被存储在pg_class.reltoastrelid里;
  • 超出的数值会被分割成chunks,并且最多toast_max_chunk_size个byte(缺省是2KB);
  • 当存储的长度超过toast_tuple_threshold(通常是2KB),就会触发toast存储;
  • toast将会压缩或者移动字段值直到超过部分比toast_tuple_targer值小(这个值通常也是2KB)

建表时自动创建toast表

dellstore2=# create table toast_1(id int);
CREATE TABLE

dellstore2=# select oid,relname,reltoastrelid from pg_class where relname = 'toast_1';
  oid  | relname | reltoastrelid 
-------+---------+---------------
 16498 | toast_1 |             0
(1 row)

dellstore2=# create table toast_2(id int,info text);--其中info字段是text类型,则会自动产生toast表来存储。
CREATE TABLE
dellstore2=# select oid,relname,reltoastrelid from pg_class where relname = 'toast_2';
  oid  | relname | reltoastrelid 
-------+---------+---------------
 16501 | toast_2 |         16504
(1 row)

dellstore2=# select oid,relname from pg_class where oid = 16504;
  oid  |    relname     
-------+----------------
 16504 | pg_toast_16501
(1 row)

更改表的存储方式为toast

语法:

ALTER TABLE [ TABLE ] ALTER [ COLUMN ] column_name SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }

dellstore2=# create table toast_0(info text);
CREATE TABLE
dellstore2=# \d+ toast_0
                      Table "public.toast_0"
 Column | Type | Modifiers | Storage  | Stats target | Description 
--------+------+-----------+----------+--------------+-------------
 info   | text |           | extended |              | 

dellstore2=# alter table toast_0 alter column info set storage main;
ALTER TABLE
dellstore2=# \d+ toast_0
                      Table "public.toast_0"
 Column | Type | Modifiers | Storage | Stats target | Description 
--------+------+-----------+---------+--------------+-------------
 info   | text |           | main    |              | 


查看toast表的名字

dellstore2=# select relname,relfilenode,reltoastrelid from pg_class where relname = 'toast_0';
 relname | relfilenode | reltoastrelid 
---------+-------------+---------------
 toast_0 |       16507 |         16510
(1 row)

dellstore2=# select relname from pg_class where oid = 16510;
    relname     
----------------
 pg_toast_16507
(1 row)

toast四种存储策略

策略 说明
PLAIN 避免压缩和行外存储。只有那些不需要TOAST策略就能存放的数据类型允许选择(例如 int类型),而对于text这类要求存储长度超过页大小的类型,是不允许采用此策略的
MAIN 允许压缩,但不许行外存储。实际上,为了保证大数据的存储,行外存储在其他方式(例如压缩)都无法满足的情况下,作为最后手段还是会被启动。因此理解为尽量不使用行外存储更贴切
EXTENDED 允许行外存储和压缩。一般会压缩,如果还是太大,就会行外存储
EXTERNA 允许行外存储,但不允许压缩。类似字符串这种会对数据的一部分进行操作的字段,采用此策略可能获得更高的性能,因为不需要读取整行数据再解压

toast表额外的三个字段

字段名 属性
chunk_id 标识TOAST表的OID字段
chunk_seq chunk的序列号,与chunk_id的组合唯一索引可以加速访问
chunk_data 存储TOAST表的实际数据
dellstore2=# select oid,relname,reltoastrelid from pg_class where relname = 'toast_0';
  oid  | relname | reltoastrelid 
-------+---------+---------------
 16507 | toast_0 |         16510
(1 row)

dellstore2=# \d+ toast_0
                      Table "public.toast_0"
 Column | Type | Modifiers | Storage | Stats target | Description 
--------+------+-----------+---------+--------------+-------------
 info   | text |           | main    |              | 


toast表的计算

  • 计算一个表的大小时要注意统计toast的大小,因为对超长字段存储时,在基础表上可能只存了20%,另外的数据都存到了toast表里面去了,计算大小时要结合起来看
  • 索引也是一样,对于表里有extended或者EXTERNA类型的会创建toast表,两者的关联是通过pg_class里的OID取关联的
dellstore2=# create table toast_t(id int,name varchar(50),info text);
CREATE TABLE
dellstore2=# select relname,relfilenode,reltoastrelid from pg_class where relname = 'toast_t';
 relname | relfilenode | reltoastrelid 
---------+-------------+---------------
 toast_t |       16513 |         16516
(1 row)

dellstore2=# insert into toast_t select generate_series(1,2),'test',repeat('t',10000);
INSERT 0 2
dellstore2=# select pg_column_size(id),pg_column_size(name),pg_column_size(info) from toast_t;
 pg_column_size | pg_column_size | pg_column_size 
----------------+----------------+----------------
              4 |              5 |            125
              4 |              5 |            125
(2 rows)

dellstore2=# select pg_size_pretty(pg_relation_size('toast_t')),pg_size_pretty(pg_relation_size(16516));
 pg_size_pretty | pg_size_pretty 
----------------+----------------
 8192 bytes     | 0 bytes
(1 row)

dellstore2=# insert into toast_t select generate_series(1,2),'test',repeat('t',100000);
INSERT 0 2
dellstore2=# select pg_size_pretty(pg_relation_size('toast_t')),pg_size_pretty(pg_relation_size(16516));
 pg_size_pretty | pg_size_pretty 
----------------+----------------
 8192 bytes     | 0 bytes
(1 row)

dellstore2=# insert into toast_t select generate_series(1,2),'test',repeat('t',200000);
INSERT 0 2
dellstore2=# select pg_size_pretty(pg_relation_size('toast_t')),pg_size_pretty(pg_relation_size(16516));
 pg_size_pretty | pg_size_pretty 
----------------+----------------
 8192 bytes     | 8192 bytes
(1 row)

dellstore2=# insert into toast_t select generate_series(1,2),'test',repeat('t',400000);
INSERT 0 2
dellstore2=# select pg_size_pretty(pg_relation_size('toast_t')),pg_size_pretty(pg_relation_size(16516));
 pg_size_pretty | pg_size_pretty 
----------------+----------------
 8192 bytes     | 16 kB
(1 row)


可以看到后插入的数据随着字段内容的增多,toast段一直在变大。基础表的大小没有变化。

toast表的优点

  1. 可以存储超长大字段,避免之前不能直接存储的限制;
  2. 物理上与普通表是分离的,检索查询时不检索到该字段会极大地加快速度;
  3. 更新普通表时,该表地toast数据没有被更新时,不用去更新toast表

toast表的缺点

  1. 对大字段的索引创建是一个问题,有可能会失败,通常不建议在大字段上创建,全文检索是一个解决方案
  2. 大字段的更新会有点慢, 其他DB也存在相同问题
posted on 2022-12-08 22:10  jl1771  阅读(287)  评论(0编辑  收藏  举报