深圳企业网站建设专业,腾讯网站建设推广,哪个网站可以做付费推广,中国哪里在大建设本文参考#xff1a;PostgreSQL TOAST 技术理解《PostgreSQL修炼之道》一、TOAST是什么#xff1f;TOAST是“The Oversized-Attribute Storage Technique”#xff08;超尺寸属性存储技术#xff09;的缩写#xff0c;主要用于存储一个大字段的值。要理解TOAST#xff0c… 本文参考PostgreSQL TOAST 技术理解《PostgreSQL修炼之道》一、TOAST是什么 TOAST是“The Oversized-Attribute Storage Technique”超尺寸属性存储技术的缩写主要用于存储一个大字段的值。 要理解TOAST我们要先理解页BLOCK的概念。在PG中页是数据在文件存储中的基本单位其大小是固定的且只能在编译期指定之后无法修改默认的大小为8KB。同时PG不允许一行数据跨页存储。那么对于超长的行数据PG就会启动TOAST将大的字段压缩或切片成多个物理行存到另一张系统表中TOAST表这种存储方式叫行外存储。二、使用TOAST 只有特定的数据类型支持TOAST因为那些整数、浮点数等不太长的数据类型是没有必要使用TOAST的。 另外支持TOAST的数据类型必须是变长的。在变长类型中前4字节32bit称为长度字长度字后面存储具体的内容或一个指针。长度字的高2bit位是标志位后面的30bit是长度值表示值的总长度包括长度字本身以字节计。由长度值可知TOAST数据类型的逻辑长度最多是30bit即1GB(2^30-1字节之内。前2bit的标志位一个表示压缩标志位一个表示是否行外存储如果两个都是零那么表示既未压缩也未行外存储。如果设置了压缩标志标志位表示该数值被压缩过使用的是非常简单且快速的LZ压缩方法使用前必须先解压缩。如果设置了行外存储标志位则表示该数值是在行外存储的。此时长度字后面的部分只是一个指针指向存储实际数据的TOAST表中的位置。如果两个标志位都设置了那么这个行外数据也会被压缩。不管是哪种情况长度字里剩下的30bit的长度值都表示数据的实际尺寸而不是压缩后的长度。 在 PG 中每个表字段有四种 TOAST 的策略PLAIN —— 避免压缩和行外存储。只有那些不需要 TOAST 策略就能存放的数据类型允许选择例如 int 类型而对于 text 这类要求存储长度超过页大小的类型是不允许采用此策略的。EXTENDED —— 允许压缩和行外存储。一般会先压缩如果还是太大就会行外存储。这是大多数可以TOAST的数据类型的默认策略。EXTERNAL —— 允许行外存储但不许压缩。这让在text类型和bytea类型字段上的子串操作更快。类似字符串这种会对数据的一部分进行操作的字段采用此策略可能获得更高的性能因为不需要读取出整行数据再解压。MAIN —— 允许压缩但不许行外存储。不过实际上为了保证过大数据的存储行外存储在其它方式例如压缩都无法满足需求的情况下作为最后手段还是会被启动。因此理解为尽量不使用行外存储更贴切。 首先创建一张 blog 表查看它的各字段的TOAST策略postgres# create table blog(id int, title text, content text);
CREATE TABLE
postgres# d blog;Table public.blogColumn | Type | Modifiers | Storage | Stats target | Description
------------------------------------------------------------------id | integer | | plain | | title | text | | extended | | content | text | | extended | | 可以看到interger 默认 TOAST 策略为 PLAIN 而 text 为 EXTENDED 。 另外可以修改某个字段系统默认分配的TOAST策略假如要将上面blog表中的content字段的TOAST策略改成EXTERNAL就可以这样ALTER TABLE blog ALTER content SET STORAGE EXTERNAL; 三、TOAST表的结构 如果一个表中有任何一个字段是可以TOAST的那么PostgreSQL会自动为该表建一个相关联的TOAST表其OID存储在pg_class系统表的reltoastrelid记录里行外的内容保存在TOAST表里。 查看blog表对应的TOAST表的OIDpostgres# select relname,relfilenode,reltoastrelid from pg_class where relnameblog;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 。 行外存储被切成了多个Chunk块每个Chunk块大约是一个BLOCK的四分之一大小如果块大小为8KB默认就是8KB则Chunk大约为2KB比2KB略小一点每个Chunk都作为独立的行存储在TOAST表中。 TOAST表有三个字段chunk_id —— 用来表示特定 TOAST 值的 OID 可以理解为具有同样 chunk_id 值的所有行组成原表这里的 blog 的 TOAST 字段的一行数据。chunk_seq —— 用来表示该行数据在整个数据中的位置。chunk_data —— 该Chunk实际的数据。 我们看下上面的TOAST表pg_toast.pg_toast_16441的定义postgres# d pg_toast.pg_toast_16441;
TOAST table pg_toast.pg_toast_16441Column | Type | Storage
------------------------------chunk_id | oid | plainchunk_seq | integer | plainchunk_data | bytea | plain 在chunk_id和chunk_seq上有一个唯一的索引提供对数值的快速检索。 因此一个表示行外存储的指针数据中包括了要查询的TOAST表的OID和特定数值的chunk_id也是一个OID类型。为了方便指针数据还存储了逻辑数据的尺寸原始的未压缩的数据长度及实际存储的尺寸如果使用了压缩则两者不同。加上头部的长度字一个TOAST指针数据的总尺寸是20字节。四、TOAST技术实践 现在我们来实际验证下TOAST: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 contentcontent||content where id1;
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 | 199616439 | 1 | 1773
(2 rows) 可以看到直到 content 的长度为327680时已远远超过页大小 8K对应 TOAST 表中才有了2行 数据且长度都是略小于2K这是因为 extended 策略下先启用了压缩然后才使用行外存储。 下面我们将 content 的 TOAST 策略改为 EXTERNAL 以禁止压缩。postgres# alter table blog alter content set storage external;
ALTER TABLE
postgres# d blog;Table public.blogColumn | 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 | 3276802 | title | 10
(2 rows) 然后重复以上步骤直到 pg_toast_16441 表中有数据postgres# update blog set contentcontent||content where id2;
UPDATE 1
postgres# select id,title,length(content) from blog;id | title | length
-------------------2 | title | 3276801 | 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 | 199616447 | 1 | 177316448 | 0 | 199616448 | 1 | 199616448 | 2 | 1996....省略16448 | 164 | 1996
(167 rows) 因为不允许压缩所以新的操作在TOAST表中生成了更多Chunk块行记录。通过以上操作得出以下结论如果策略允许压缩则TOAST优先选择压缩。不管是否压缩一旦数据超过2KB左右就会启用行外存储。修改TOAST策略不会影响现有数据的存储方式。五、TOAST技术总结 TOAST比那些更直接的方法比如允许行值跨越多个页面有更多优点。 假设查询通常是用相对比较短的键值进行匹配的那么执行器的大多数工作都将使用主行项完成。TOAST过的属性的大值只是在把结果集发送给客户端的时候才被抽出来如果它被选中。 因此主表要小得多并且它的能放入到共享缓冲区中的行要比没有任何行外存储的方案更多。 排序集也缩小了并且排序将更多地在内存里完成。一个小测试表明一个典型的保存 HTML 页面以及它们的 URL 的表占用的存储包括TOAST表在内大约只有裸数据的一半而主表只包含全部数据的 10%URL和一些小的 HTML 页面。与在一个非TOAST的对照表里面存储把全部 HTML 页面裁剪成 7Kb 以匹配页面大小同样的数据相比运行时没有任何区别。