PostgreSQL数据库不允许元组(行,记录)跨越多个页面(page)存储,所以,它不能直接存储非常大的字段值。对于大字段值,它将被压缩且(或)分解为多个物理行,该技术称为“TOAST”。
toast技术特点
- toast是超长字段在PG的一个存储方式;
- 全称The overSized Attribute Stroage Technique(超尺寸字段存储技术);
- 它会将大字段值压缩或者分散为多个物理行来存储;
- 对于用户来说不用关注这一技术实现,完全是透明的;
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表的优点
- 可以存储超长大字段,避免之前不能直接存储的限制;
- 物理上与普通表是分离的,检索查询时不检索到该字段会极大地加快速度;
- 更新普通表时,该表地toast数据没有被更新时,不用去更新toast表
toast表的缺点
- 对大字段的索引创建是一个问题,有可能会失败,通常不建议在大字段上创建,全文检索是一个解决方案
- 大字段的更新会有点慢, 其他DB也存在相同问题