数据库 频道

从PG的TOAST存储谈起:实现方式、存在问题与优化

数据库存储引擎采用的技术会对数据库的某些应用场景的性能产生十分重要的影响。可能很多朋友并不关注这一点,也没有遇到过那些网上所说的问题,这可能你的应用场景并没有遇到哪种情况,或者说你的系统的规模还不大,硬件性能能够弥补这方面的不足。不过作为数据库从业人员,不管是应用架构师、研发人员还是DBA最好还是了解一些这方面的知识,从而避免此类问题发生或者当遇到类似性能问题的时候能够快速定位问题。

Oracle数据库通过行链和行迁移来处理块中无法存储一条完整数据的场景。大量VARCHAR字段做UPDATE时很容易产生行迁移的现象,如果一张表中的行迁移的数据量比较多的时候,此类数据的访问性能会下降。PG数据库没有使用行链行迁移这样的处理方式,一条记录必须存储在同一个数据块中,不能使用行链。因此POSTGRESQL数据库采用 TOAST 存储机制来处理一行数据无法在同一个数据块中存储的问题。TOAST 技术会将一行中的部分无法存储在主表中的字段压缩或切片成多个物理行存储在另一张系统表中。TOAST 存储技术的优势在于其灵活性,可以根据字段的逻辑特性来设置是否需要压缩、是否需要扩展,并且可以调整某些阈值以提高存储的压缩率或读取数据的性能。

在PG数据库中,当一行数据超过 TOAST_TUPLE_THRESHOLD(默认为块大小的1/4,默认块大小的情况下为2KB)时,PostgreSQL 将压缩数据从而满足这个条件。如果压缩后还是超过了这个阈值,那么它将被拆分为更小的块,并存储在相关 TOAST 表中的多个物理行(行外存储)。

对于某类字段的默认存储类型,pg_type中有明确的定义。我们来看看它们的含义。

m = 主:这意味着没有压缩,没有离线存储。这适用于根本不可 TOAST 的数据类型。

p = 普通:压缩,但没有离线存储。

x = 扩展:压缩和离线存储。

e = 外部:意味着只存储在表的外部。

我们来看上面的例子,我创建了一张包含了4个字段的表,其中col1是numeric类型的,其存储类型是main,不可压缩和表外存储。P表示可压缩,但是不行外存储。

下面我们简单的来看一个例子,看看TOAST是如何发挥作用的。

我们创建了一张表,明显这张表的行可能超过2000字节,那么系统自动为这张表创建了一个toast对象,也就是一张系统表。通过pg_class中的reltoastrelid可以找到这张表的信息。

我们先向这张表中写入了一些数据,不过行长度不超过TOAST_TUPLE_THRESHOLD,这时候我们看到在toast系统表中没有写入数据。这是符合我们的预期的。

接下来我们做一个UPDATE,不过UPDATE采用了可压缩的字符串,虽然把行长度UPDATE到了大于2000字节,不过数据依然存储在数据块中,比没有写入toast对象。在能Inline存储的时候尽可能不用TOAST,这是从性能考虑的必然选择。

接下来我们用随机字符串update,这时候无法把一行压缩到2000以内了,此时我们发现100行数据产生了100个toast数据行。这和PG对于TOAST的描述是完全相同的。

其实今天我们来讨论TOAST,并不是探究TOAST的算法是如何的,TOAST的实现细节对于大多数DBA或者数据库用户来说并不重要。而是如何尽可能避免TOAST带来的负面影响,并如何尽可能的利用TOAST技术来优化我们的应用。

场景1:表中带有大量的平时不太访问的字段,这些字段平时很少更新,而其他字段经常做范围扫描甚至全表、全分区扫描。

这种场景我们可以考虑把不太常用的字段都放入TOAST里,从而优化常见场景的访问性能。可以参考下面的例子。

场景2:表字段很长或者列数很多,一般都会超过2000字节。而且访问频繁,如何优化访问性能。

这种情况最好能使用能够较大的PAGE大小,PG数据库没有Oracle那么灵活。如果只能使用8K大小的PAGE,那么需要个性化设置TOAST_TUPLE_THRESHOLD,幸运的是PG 11以后支持了表级的设置。如果没有其他更好的优化方法,那么对该表做拆解可能是最好的做法。

除此之外,对于TOAST,压缩算法也十分重要,PG支持多种压缩算法(不同版本支持的不同),可以通过测试,在压缩比与资源开销之间选择好平衡,选择一种适合你的应用场景的压缩算法用于TOAST是十分必要的。

时间有限,今天只是简单的分析了一下TOAST,并列举了两个小场景供大家参考。实际上没有哪个技术是最好的,也没有哪个技术是不好的,扬长避短是数据库应用中的不二法门,不要纠结于某个数据库的某个技术存在某种缺陷,找到更好的利用某种技术的方法才能用好某个数据库。

0
相关文章