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

TOAST是“The Oversized-Attribute Storage Technique”的缩写,主要用于存储一个大字段的值。要理解TOAST,我们要先理解页(BLOCK)的概念。在PG中,页是数据在文件存储中的基本单位,其大小是固定的且只能在编译期指定,之后无法修改,默认的大小为8KB。同时,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策略,不会影响现有数据的存储方式

相关阅读:

存储总量达20T的MySQL实例,如何完成迁移?

腾讯存储与云存储Redis免费体验


此文已由作者授权腾讯云技术社区发布,转载请注明文章出处,原文链接https://www.qcloud.com/community/article/164816001481011925

获取更多云计算技术干货,可请前往腾讯云技术社区,欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~

腾讯云数据库团队:PostgreSQL TOAST技术理解的更多相关文章

  1. 腾讯云数据库团队:浅谈如何对MySQL内核进行深度优化

    作者介绍:简怀兵,腾讯云数据库团队高级工程师,负责腾讯云CDB内核及基础设施建设:先后供职于Thomson Reuters和YY等公司,PTimeDB作者,曾获一项发明专利:从事MySQL内核开发工作 ...

  2. 腾讯云数据库团队:MySQL AHI 实现解析

    MySQL 定位用户记录的过程可以描述为:打开索引 -> 根据索引键值逐层查找 B+ 树 branch 结点 -> 定位到叶子结点,将 cursor 定位到满足条件的 rec 上:如果树高 ...

  3. 腾讯云数据库团队:MySQL数据库的高可用性分析

    作者介绍:易固武,腾讯高级工程师,参与腾讯账号安全建设,腾讯数据仓库(TDW)优化改造,腾讯云数据库等项目,对大规模分布式存储和计算系统有浓厚的兴趣和经历 MySQL数据库是目前开源应用最大的关系型数 ...

  4. 腾讯云数据库团队:phpMyAdmin中sql-parser组件的使用

    phpMyAdmin是一款基于Web端运行的开源数据库管理工具,支持管理MySQL和MariaDB两种数据库. phpMyAdmin的程序主要使用php和javascript开发,它的安装使用都比较简 ...

  5. 腾讯云数据库团队:SQL Server 数据加密功能解析

    数据加密是数据库被破解.物理介质被盗.备份被窃取的最后一道防线:数据加密,一方面解决数据被窃取安全问题,另一方面有关法律要求强制加密数据:SQL Server 的数据加密相较于其他数据库,功能相对完善 ...

  6. 腾讯云数据库团队:MySQL语句复制(SBR)的缺陷列举

    作者介绍: 赵伟 腾讯云TDSQL数据库开发者 MySQL (这里的MySQL是指广义的mysql,包括oracle,mysql,percona,mariadb等)的Statement Based R ...

  7. 腾讯云数据库团队:MySQL5.7 JSON实现简单介绍

    作者介绍:吴双桥 腾讯云project师 阅读原文.很多其它技术干货.请訪问fromSource=gwzcw.57435.57435.57435">腾云阁. 本文主要介绍在MySQL ...

  8. MySQL之父造访腾讯云 为腾讯云数据库开源点赞

    近日,技术大牛 MariaDB 公司创始人兼CTO Michael Widenius(又名Monty).MariaDB 基金会主席 Kaj 来到中国,针对MariaDB与腾讯云的技术合作进行回访.去年 ...

  9. TDSQL|三篇论文入选国际顶会SIGMOD,厉害了腾讯云数据库

    好消息!6月13日,腾讯云数据库三篇论文再次入选数据库行业顶会SIGMOD,被SIGMOD 2022 Research Full Paper(研究类长文)收录. 本次被收录的研究成果中,新型数据结构设 ...

随机推荐

  1. Python抓取第一网贷中国网贷理财每日收益率指数

    链接:http://www.p2p001.com/licai/index/id/147.html 所需获取数据链接类似于:http://www.p2p001.com/licai/shownews/id ...

  2. MASMPlus编译出错:error LNK2001: unresolved external symbol _WinMainCRTStartup

    初学汇编,感觉很多不懂.不过那也是,如果懂了的话就不用学了,从无到有学习一门编程语言果然不是那么容易的一件事. 学习汇编总得要有一款汇编软件才行,没理由只是使用Windows自带的DEBUG.于是上了 ...

  3. [学习心得][Introduction to ASP.NET Core 1.0]3-2 ASP.NET Core and MVC Pattern

    We need SMART Models, THIN Controllers, and DUMB Views. VeryBeginning 要使用MVC,要先将MVC服务加到程序中去 Nuget包里添 ...

  4. 2013~2014年度 NOIP~GDOI总结

    滚回去撸一年中考撸完之后就迎来了NOIP2013(话说初赛差点被坑了有木有= =)想想当年10月还是那样的天真的去了广州,结果就被虐成翔的回来了= =只做了三道签到题的渣渣就是弱= =DAY1T2死活 ...

  5. 在 Windows 上测试 Redis Cluster的集群填坑笔记

    redis 集群实现的原理请参考http://www.tuicool.com/articles/VvIZje       集群环境至少需要3个节点.推荐使用6个节点配置,即3个主节点,3个从节点. 新 ...

  6. DDD中的分层架构

    DDD中的分层架构很好的应用了关注点分离原则Separation of Concerns(SOC),每一层做好自己的事情,减少交叉 表现层 表现层提供用来完成任务的用户界面,如webform wpf ...

  7. 规范 : angular ui router path & params

    在seo文章中提到url的path 必须是 why-us,而不是whyUS 所以定了规范,所有的path 必须why-us path ?后尾的是用来filter的,所以可以WhyUs 如果是不需要给s ...

  8. angular ui-router 正则

    {id:[0-9a-fA-F]{1,8}} 如果在找ui-router 时,会看到以上的正则 我解释一下 id是param,可以把必备的结果收起来 []里面的必配的正则,但只是给一个string {} ...

  9. nginx负载均衡2

    负载均衡2 网站是发展初期,nginx只代理了后端一台服务器,但由于网站名气大涨访问的人越来越多一台服务器实在是顶不住,于是我们加了多台服务器,那么多台服务器又怎么配置代理呢,这里以两台服务器为案例, ...

  10. Spring+SpringMVC+MyBatis+easyUI整合基础篇(七)JDBC url的连接参数

    在java程序与数据库连接的编程中,mysql jdbc url格式如下: jdbc:mysql://[host:port],[host:port].../[database][?参数名1][=参数值 ...