浅谈PostgreSQL之TOAST技术


目录

浅谈PostgreSQL之TOAST技术(一)

背景介绍

Oracle数据的存储

什么是LOBs

LOB的存储如何实现

PostgreSQL数据的存储

什么是TOAST

TOAST 存储策略

TOAST如何实现

Oracle LOB和TOAST比较

相同点

不同点

结语


背景介绍

最近有几套Oracle数据库要迁移到一款基于PostgreSQL的国产化数据库,迁移后发现数据“缩水”了,原本一套数据量为10T的Oracle数据库,全量迁移到国产数据库后只有 4.4T。是源库的碎片化严重还是数据被压缩了?带着这样的疑问,本文我们就来介绍一下,Oracle和PostgreSQL这两款非常优秀的关系型数据库,在数据存储方面有什么差别。


Oracle数据的存储

Oracle中常用的数据类型,像char、varchar、varchar2,不管是定长还是变长的数据类型,都有其最大的存储大小限制(char为2000 字节, varchar和varchar2为4000 字节),那么要存储超过字段最大长度的数据该怎么办呢,答案就是Large Objects (LOBs),这里可能有同学说还有long和long row啊,由于这两种数据类型是Oracle为了向后兼容才保留的,所以我们这里先不做讨论。


什么是LOBs 

LOBs 是Oracle数据库中用来存储超长数据的数据类型的集合,可以存储超过4000个字节的字符串、二进制数据、OS文件等大对象信息,其最大的存储容量可以达到 8TB~128T。其中LOBs又分为内部LOB和外部LOB,顾名思义,内部LOB是指数据存储在数据库表空间内部,包括BLOB、CLOB、NCLOB三种数据类型,外部LOB是指数据存储在数据库表空间以外的操作系统文件中,只包括BFILE一种数据类型,我们通常把内部LOB也简称为LOB。


LOB的存储如何实现

对于LOB类型数据的存储来说,默认情况下,LOB存储的数据小于4000 字节时,就会像VARCHAR2一样存储在表中。当LOB存储的数据超过了4,000字节时,会“移出”到单独的LOB段 。每一个被“移出”的LOB数据又称为一个LOB实例,LOB实例由存储在表中LOB列上的locator (定位器)和存储在LOB段中的实际数据组成,定位器指向实际数据的物理位置。LOB段是由chunk组成的 ,而chunk是由逻辑上连续的一个或多个数据库块(block)组成,chunk大小默认等于数据库块大小(默认8K),最大为32K,chunk也是LOB段的最小分配单元,即每个存储在LOB段中的数据值至少会占用一个CHUNK。数据大小和CHUNK大小的关系如下图。

PostgreSQL数据的存储

而在PostgreSQL中,像varchar,char,text,bytes 这些数据类型是可以接受任何长度的字段值的。为何PostgreSQL可以支持存储任何长度的字段?其实对于非常大的字段值来说,仍然存储在行内显然是不合适的,PostgreSQL底层通过引入了TOAST技术来解决这个问题。


什么是TOAST

PostgreSQL使用固定的页面尺寸(通常是8kB),并且不允许元组跨越多个页面(避免了Oracle中行迁移和行链接带来的性能影响),因此不能直接存储非常大的字段值。为了解决这个问题,大的字段值会被压缩并/或分解成多个物理行。这些处理对用户都是透明的,只是在大部分的后端代码上有一些小的影响。这个技术称为TOAST(超尺寸属性存储技术-The Oversized-Attribute Storage Technique)。


TOAST 存储策略

PostgreSQL中大部分数据类型支持TOAST存储,对于像整型,浮点型这类无法存储大量数据的数据类型来说,也不需要支持。而且要支持TOAST,数据类型必须是变长类型, 


PostgreSQL中字段类型有如下四种存储策略:

PLAIN  不允许压缩或行外存储,适用于不可使用TOAST特性的数据类型

EXTENDED 允许压缩和行外存储,当行长度超过TOAST_TUPLE_THRESHOLD (通常为2000 字节)时先进行压缩,压缩后长度仍然超过 TOAST_TUPLE_THRESHOLD则进行行外存储,该策略为可使用TOAST特性的数据 类型的默认策略

EXTERNAL 只允许行外存储而不对数据进行压缩

MAIN 只压缩不进行行外存储(但实际上该策略仍然会进行行外存储)


注:

TOAST_TUPLE_THRESHOLD值可以通过 ALTER TABLE ... SET (toast_tuple_target = N) 命令修改

2  行长度超过TOAST_TUPLE_THRESHOLD值会触发TOAST特性,而不是字段值长度。

查看字段类型对应的存储策略

默认情况下数据的存储方式

TOAST如何实现

每个包含支持TOAST特性的数据类型的表,系统会默认生成一个与之对应的TOAST表,通过如下语句查询该TOAST表,表名为pg_toast.pg_toast_ (当前表的relfilenode),如果表被truncate,其relfilenode值会改变,但关联的TOAST表名不会变。

如下结果所示:

如果表被truncate,可以根据如下语句查找其关联的TOAST表名:

每个使得表中行长度超过TOAST_TUPLE_THRESHOLD(2000)个字节的字段值会被分割成TOAST_MAX_CHUNK_SIZE(2000)字节大小的chunk其中TOAST表有三个字段,分别为chunk_id, chunk_seq和chunk_data:

chunk_id 每个超过2000 个字节的字段值对应一个chunk_id

chunk_data 存储实际的字段值,由大小为2000个字节的chunk构成(每个chunk中前4个字节存储该chunk的长度,所以每个chunk的实际存储容量为1996 bytes),如下图所示

chunk_seq代表chunk在该字段值中的顺序。

如下实验可以看到字段值和TOAST表的关系(使用external存储策略进行实验):


可以看到TOAST表中chunk_data的长度(1996+1996+1008) 正好和插入的字段值长度相等。


Oracle LOB和TOAST比较

相同点 

1 存储方式相同:Oracle 中LOB对象和PostgreSQL中TOAST对象的存储方式类似,默认情况都是在行内存储,当数据量达到临界值则会在行外存储。

2 目的相同:都是为了提高表的读写速率,因为超长字段的读写频率远低于其他字段,行外存储就相当于对冷热数据进行了分离。


不同点

1 LOB对象是在单个字段值长度达到阈值则进行表外存储,TOAST则是在单行数据长度达到阈值时,按每个字段的长度由大到小依次存储到TOAST表,原表行长度始终小于该阈值。

2 默认情况下Oracle LOB不对存储的数据压缩,而TOAST会压缩数据。

如下实验,可以看到TOAST表中依次存储的是col3和col2两个字段。

结语

Postgresql默认会对达到一定阈值条件的数据进行压缩存储,所以从oracle迁移过来的数据量变小也是正常的现象。透过现象看本质,很多不同数据库中数据的存储虽然表象上是千差万别的,但其内部的存储原理其实大同小异。这套oracle数据库迁移到国产数据库后,整体数据量压缩了近60%,某张2.6T的大表迁移后只有300G,压缩率更是达到了88%,当然一部分原因可能是oracle高水位线导致,即便如此,对于大体量的数据库来说这也是非常有用的。


后面我们会对PostgreSQL数据库中的数据是如何压缩的、实际迁移后的压缩比率问题以及更多有意思的技术进行刨析分解,让租户加深对数据库的了解,方便业务选型最适合自己的Database。



本文分享自微信公众号 - 开源软件联盟PostgreSQL分会(kaiyuanlianmeng)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部