↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0
历史版本。 PostgreSQL 8.0 已结束支持。 请参阅 当前版本手册.

49.2. TOAST #

本节概述TOAST(超长属性存储技术)。

由于 PostgreSQL 使用固定页面大小(通常是 8Kb),并且不允许元组跨越多个页面,因此无法直接存储非常大的字段值。在 PostgreSQL 7.1 之前,一个表行可以存放的数据总量有一个略小于一页的硬限制。在 7.1 及之后的版本中,这一限制通过允许压缩大字段值并且/或者把它们拆分成多个物理行来克服。这个过程对用户是透明的,而且对大部分后端代码的影响很小。这项技术被亲切地称为 TOAST(或者“自切片面包问世以来最棒的东西”)。

只有某些数据类型支持 TOAST,因为没有必要让那些不可能产生大字段值的数据类型承担这部分额外开销。要支持 TOAST,数据类型必须具有变长(varlena)表示形式,其中任何已存储值的第一个 32 位字都包含该值以字节计的总长度(包括其自身)。TOAST 不约束表示形式的其余部分。所有支持可 TOAST 数据类型的 C 级函数都必须小心处理已 TOAST 化的输入值。(通常是在对输入值做任何处理之前调用 PG_DETOAST_DATUM,但在某些情况下也可以采用更高效的方法。)

TOAST 占用 varlena 长度字的高位两个位,从而把任何可 TOAST 数据类型值的逻辑大小限制为 1Gb(230 - 1 字节)。当这两个位都为零时,该值就是这种数据类型的普通、未经 TOAST 处理的值。其中一位如果被置位,表示该值经过压缩,使用前必须先解压。另一位如果被置位,表示该值已被线外存储。在这种情况下,该值的其余部分实际上只是一个指针,正确的数据必须在别处查找。当两个位都被置位时,线外数据也经过了压缩。在每种情况下,varlena 字低位中的长度都给出该 datum 的实际大小,而不是通过解压或取回线外数据将得到的逻辑值的大小。

如果一个表的任意列是可 TOAST 的,该表就会有一个相关联的 TOAST 表,其 OID 存储在该表的 pg_class.reltoastrelid 项中。线外 TOAST 化值保存在 TOAST 表中,下文会更详细地描述。

所用的压缩技术是 LZ 压缩技术族中一种相当简单且非常快速的方法。详情见 src/backend/utils/adt/pg_lzcompress.c。

线外值会被划分成最多 TOAST_MAX_CHUNK_SIZE 字节的块(这个值比 BLCKSZ/4 略小,默认大约为 2000 字节)。每个块都作为独立的一行,存储在所属表的 TOAST 表中。每个 TOAST 表都有 chunk_id 列(标识某个特定 TOAST 化值的 OID)、chunk_seq 列(该块在其值内的序号)以及 chunk_data 列(该块的实际数据)。在 chunk_id 和 chunk_seq 上建立的唯一索引可提供快速检索。因此,一个表示线外 TOAST 化值的指针 datum,需要存储要查找的 TOAST 表的 OID,以及该特定值的 OID(即其 chunk_id)。为方便起见,指针 datum 还会存储逻辑 datum 大小(原始未压缩数据长度)和物理存储大小(如果应用了压缩,这两者会不同)。再加上 varlena 头部字,TOAST 指针 datum 的总大小因此恒为 20 字节,与所表示值的实际大小无关。

只有当要存入表中的行值宽于 BLCKSZ/4 字节(通常是 2Kb)时,TOAST 代码才会被触发。TOAST 代码会压缩并且/或者把字段值移到线外,直到该行值不超过 BLCKSZ/4 字节,或者已经无法再获得更多收益。在 UPDATE 操作中,未更改字段的值通常会原样保留;因此,如果更新一行时其线外值都没有变化,便不会产生 TOAST 成本。

TOAST 代码为存储可 TOAST 的列识别出四种不同的策略:

  • PLAIN 既不允许压缩,也不允许线外存储。这是不可 TOAST 数据类型列唯一可能的策略。

  • EXTENDED 同时允许压缩和线外存储。这是大多数可 TOAST 数据类型的默认策略。系统会先尝试压缩,如果行仍然过大,再使用线外存储。

  • EXTERNAL 允许线外存储,但不允许压缩。使用 EXTERNAL 会让宽 text 和 bytea 列上的子串操作更快(代价是占用更多存储空间),因为在值未压缩时,这些操作经过优化,只需提取线外值中所需的部分。

  • MAIN 允许压缩,但不允许线外存储。(实际上,对于这类列,仍然可能进行线外存储,但只有在别无他法、必须这样做才能让行足够小以放入页面时,才会把它作为最后手段。)

每种可 TOAST 的数据类型都会为该数据类型的列指定默认策略,但可以通过以下命令更改某个表列的策略:ALTER TABLE SET STORAGE。

与允许行值跨页之类更直接的方法相比,这种方案有不少优点。假定查询通常是通过与相对较小的键值比较来限定的,执行器的大部分工作都会只使用主行项完成。TOAST 化属性的大值只有在结果集发送给客户端时才会被取出(前提是它们确实被选中了)。因此,主表会小得多,其更多的行能够装入共享缓冲区缓存,这是没有线外存储时做不到的。排序集也会缩小,因此排序更常能够完全在内存中完成。一个小测试表明,一个包含典型 HTML 页面及其 URL 的表,其总存储量(包括 TOAST 表)大约只有原始数据大小的一半,而主表只包含全部数据的大约 10%(URL 以及一些较小的 HTML 页面)。与未进行 TOAST 处理的对照表相比,运行时没有差异;在那个对照表中,所有 HTML 页面都被裁剪到 7 kB 以内以便放得下。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.