TOAST 是 "The Oversized-Attribute Storage Technique" 的缩写,主要用于存储一个大字段的值。要理解 TOAST,我们要先理解页(BLOCK)的概念。在 PG 中,页是数据在文件存储中的基本单位,其大小是固定的且只能在编译期指定,之后无法修改,默认的大小为 8KB。同时,PG 不允许一行数据跨页存储,那么对于超长的行数据,PG 就会启动 TOAST,具体就是采用压缩和切片的方式。如果启用了切片,实际数据存储在另一张系统表的多个行中,这张表就叫 TOAST 表,这种存储方式叫行外存储。
在深入细节之前,我们要先了解,在 PG 中每个表字段有四种 TOAST 的策略:
首先创建一张 blog 表:
- postgres=# create table blog(id int, title text, content text);
- CREATE TABLE
- postgres=# \d+ blog;
- Table "public.blog"
- Column | Type | Modifiers | Storage | Stats target | Description
- ---------+---------+-----------+----------+--------------+-------------
- id | integer | | plain | |
- title | text | | extended | |
- content | text | | extended | |
可以看到,interger 默认 TOAST 策略为 plain,而 text 为 extended。PG 资料告诉我们,如果表中有字段需要 TOAST,那么系统会自动创建一张 TOAST 表负责行外存储,那么这张表在哪里?
- postgres=# select relname,relfilenode,reltoastrelid from pg_class where relname='blog';
- relname | relfilenode | reltoastrelid
- ---------+-------------+---------------
- blog | 16441 | 16444
- (1 row)
通过上诉语句,我们查到 blog 表的 oid 为 16441,其对应 TOAST 表的 oid 为 16444(关于 oid 和 pg_class 的概念,请参考),那么其对应 TOAST 表名则为:pg_toast.pg_toast_16441(注意这里是 blog 表的 oid),我们看下其定义:
- postgres=# \d+ pg_toast.pg_toast_16441;
- TOAST table "pg_toast.pg_toast_16441"
- Column | Type | Storage
- ------------+---------+---------
- chunk_id | oid | plain
- chunk_seq | integer | plain
- chunk_data | bytea | plain
TOAST 表有 3 个字段:
- postgres=# insert into blog values(1, 'title', '0123456789');
- INSERT 0 1
- postgres=# select * from blog;
- id | title | content
- ----+-------+------------
- 1 | title | 0123456789
- (1 row)
- postgres=# select * from pg_toast.pg_toast_16441;
- chunk_id | chunk_seq | chunk_data
- ----------+-----------+------------
- (0 rows)
可以看到因为 content 只有 10 个字符,所以没有压缩,也没有行外存储。然后我们使用如下 SQL 语句增加 content 的长度,每次增长 1 倍,同时观察 content 的长度,看看会发生什么情况?
- postgres=# update blog set content=content||content where id=1;
- UPDATE 1
- postgres=# select id,title,length(content) from blog;
- id | title | length
- ----+-------+--------
- 1 | title | 20
- (1 row)
- postgres=# select * from pg_toast.pg_toast_16441;
- chunk_id | chunk_seq | chunk_data
- ----------+-----------+------------
- (0 rows)
反复执行如上过程,直到 pg_toast_16441 表中有数据:
- postgres=# select id,title,length(content) from blog;
- id | title | length
- ----+-------+--------
- 1 | title | 327680
- (1 row)
- postgres=# select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_16441;
- chunk_id | chunk_seq | length
- ----------+-----------+--------
- 16439 | 0 | 1996
- 16439 | 1 | 1773
- (2 rows)
可以看到,直到 content 的长度为 327680 时(已远远超过页大小 8K),对应 TOAST 表中才有了 2 行数据,且长度都是略小于 2K,这是因为 extended 策略下,先启用了压缩,然后才使用行外存储
下面我们将 content 的 TOAST 策略改为 EXTERNA,以禁止压缩。
- postgres=# alter table blog alter content set storage external;
- ALTER TABLE
- postgres=# \d+ blog;
- Table "public.blog"
- Column | Type | Modifiers | Storage | Stats target | Description
- ---------+---------+-----------+----------+--------------+-------------
- id | integer | | plain | |
- title | text | | extended | |
- content | text | | external | |
然后我们再插入一条数据:
- postgres=# insert into blog values(2, 'title', '0123456789');
- INSERT 0 1
- postgres=# select id,title,length(content) from blog;
- id | title | length
- ----+-------+--------
- 1 | title | 327680
- 2 | title | 10
- (2 rows)
然后重复以上步骤,直到 TOAST 表中产生新的行:
- postgres=# update blog set content=content||content where id=2;
- UPDATE 1
- postgres=# select id,title,length(content) from blog;
- id | title | length
- ----+-------+--------
- 2 | title | 2560
- 1 | title | 327680
- (2 rows)
- postgres=# select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_16441;
- chunk_id | chunk_seq | length
- ----------+-----------+--------
- 16447 | 0 | 1996
- 16447 | 1 | 1773
- 16448 | 0 | 1996
- 16448 | 1 | 564
- (4 rows)
这次我们看到当 content 长度达到 2560(按照官方文档,应该是超过 2KB 左右),TOAST 表中产生了新的 2 条 chunk_id 为 16448 的行,且 2 行数据的 chunk_data 的长度之和正好等于 2560。通过以上操作得出以下结论:
来源: http://www.cnblogs.com/qcloud1001/p/6641088.html