腾讯云数据库团队:PostgreSQL TOAST 技术理解

腾讯云技术社区-掘金主页持续为你们呈现云计算技术文章,欢迎你们关注!javascript


做者介绍:胡彬 腾讯云高级工程师html

TOAST是“The Oversized-Attribute Storage Technique”的缩写,主要用于存储一个大字段的值。要理解TOAST,咱们要先理解页(BLOCK)的概念。在PG中,页是数据在文件存储中的基本单位,其大小是固定的且只能在编译期指定,以后没法修改,默认的大小为8KB。同时,PG不容许一行数据跨页存储,那么对于超长的行数据,PG就会启动TOAST,具体就是采用压缩和切片的方式。若是启用了切片,实际数据存储在另外一张系统表的多个行中,这张表就叫TOAST表,这种存储方式叫行外存储。java

在深刻细节以前,咱们要先了解,在PG中每一个表字段有四种TOAST的策略:node

  • PLAIN:避免压缩和行外存储。只有那些不须要TOAST策略就能存放的数据类型容许选择(例如int类型),而对于text这类要求存储长度超过页大小的类型,是不容许采用此策略的sql

  • EXTENDED:容许压缩和行外存储。通常会先压缩,若是仍是太大,就会行外存储post

  • EXTERNA:容许行外存储,但不准压缩。相似字符串这种会对数据的一部分进行操做的字段,采用此策略可能得到更高的性能,由于不须要读取出整行数据再解压。性能

  • MAIN:容许压缩,但不准行外存储。不过实际上,为了保证过大数据的存储,行外存储在其它方式(例如压缩)都没法知足需求的状况下,做为最后手段仍是会被启动。所以理解为:尽可能不使用行外存储更贴切。
    如今咱们经过实际操做来研究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表负责行外存储,那么这张表在哪里?spa

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的概念,请参考PG官方文档),那么其对应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个字段:

  • chunk_id:用来表示特定TOAST值的OID,能够理解为具备一样chunk_id值的全部行组成原表(这里的blog)的TOAST字段的一行数据

  • chunk_seq:用来表示该行数据在整个数据中的位置

  • chunk_data:实际存储的数据。
    如今咱们来实际验证下:

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。经过以上操做得出如下结论:

此文已由做者受权腾讯云技术社区发布,转载请注明文章出处
原文连接:www.qcloud.com/community/a…
获取更多腾讯海量技术实践干货,欢迎你们前每每腾讯云技术社区

相关文章
相关标签/搜索