PostgreSQL TOAST 技术理解

作者:胡彬 腾讯云高级工程师

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

在深入细节之前,我们要先了解,在 PG 中每个表字段有四种 TOAST 的策略:

  • PLAIN :避免压缩和行外存储。只有那些不需要 TOAST 策略就能存放的数据类型允许选择(例如 int 类型),而对于 text 这类要求存储长度超过页大小的类型,是不允许采用此策略的
  • EXTENDED :允许压缩和行外存储。一般会先压缩,如果还是太大,就会行外存储
  • 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 表负责行外存储,那么这张表在哪里?

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。通过以上操作得出以下结论:

  • 如果策略允许压缩,则TOAST优先选择压缩。
  • 不管是否压缩,一旦数据超过2KB左右,就会启用行外存储。
  • 修改TOAST策略,不会影响现有数据的存储方式。

相关推荐 在云端快速构建一个 ODOO 站点 微信支付商户系统架构背后的故事

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一名叫大蕉的程序员

今天聊聊分布式锁 No.86

首先祝大家新年快乐,感谢大家过去一年的陪伴。大蕉在这里给大家拜年啦啦。新年快乐,我爱学习。 恭喜发财,红包拿来~ 好了切入正题,一直在工作中会聊到很多锁的问题,...

46613
来自专栏文渊之博

列存储索引1:初识列存储索引

     2012以后提供了一种不同于传统B树结构的索引类型,就是内存列存储索引。这种索引应用了一种基于列的存储模式,也是一种新的查询执行的批处理模式,并且为特...

1985
来自专栏13blog.site

Spring+SpringMVC+MyBatis+easyUI整合优化篇(十二)数据层优化-explain关键字及慢sql优化

本文提要 从编码角度来优化数据层的话,我首先会去查一下项目中运行的sql语句,定位到瓶颈是否出现在这里,首先去优化sql语句,而慢sql就是其中的主要优化对象,...

34711
来自专栏乐沙弥的世界

收缩Oracle数据文件

      最近有网友提到收缩Oracle数据文件的问题,这是DBA经常碰到的一个常见问题。通常我们需要收缩相应的数据文件以减少来自磁盘空间的压力以及提高数据库...

672
来自专栏腾讯数据库技术

听原作者为你深度解读InnoDB批量建索引原理

2113
来自专栏数据和云

Oracle 12c新特性:多租户中使用 CONTAINERS 语句跨越PDB查询

张乐奕 云和恩墨副总经理,Oracle ACE总监,ACOUG 联合创始人 在最新版本的 Oracle Database 12.1.0.2 中,新特性提供了 ...

2805
来自专栏Java学习123

IBM WebSphere MQ 系列(四) 使用MQ命令

4356
来自专栏沃趣科技

配置详解 | performance_schema全方位介绍

在上一篇 《初相识 | performance_schema全方位介绍》 中粗略介绍了如何配置与使用performance_schema,相信大家对perfor...

7547
来自专栏友弟技术工作室

Mysql大表优化方案

原文版权 ? 当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆...

3497
来自专栏码字搬砖

kylin报错 java.lang.ArrayIndexOutOfBoundsException: -1

当用kylin做报表时,kylin报错 2017-06-26 14:51:52,523 ERROR [IPC Server handler 3 on 330...

1064

扫码关注云+社区