选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0
历史版本PostgreSQL 9.1 已于 2016 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

55.2. TOAST #

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

PostgreSQL 使用固定页面大小(通常为 8 kB),并且不允许 元组跨越多个页面。因此,无法直接存储非常大的字段值。为克服这一限制,大字段值会被 压缩并且/或者拆分成多个物理行。这个过程对用户是透明的,而且对大部分后端代码的影响 很小。这项技术被亲切地称为 TOAST(或者 自切片面包问世以来最棒的东西)。

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

TOAST 会占用 varlena 长度字中的两个位 (在大端机器上是最高位,在小端机器上是最低位),从而把任何可 TOAST 数据类型值的逻辑大小限制为 1 GB (230 - 1 字节)。当这两个位都为零时, 该值就是这种数据类型的普通、未经 TOAST 处理的值,长度字的其余位 给出该 datum 的总大小(包括长度字),以字节计。当最高位或最低位被置位时, 该值使用的不是通常的四字节头部,而是单字节头部;该字节 的其余位给出 datum 的总大小(包括长度字节),以字节计。作为特殊情况, 如果其余位全为零(对于自包含长度而言这是不可能的),则该值是指向存储在单独 TOAST 表中的线外数据的指针。(TOAST 指针的大小由 datum 的第二个字节给出。)带单字节头部的值同样不按任何特定边界 对齐。最后,当最高位或最低位为零、但相邻的一位被置位时,该 datum 的内容已被压缩,使用前必须先解压。在这种情况下,长度字的其余位给出的 是压缩后 datum 的总大小,而不是原始数据的大小。请注意,线外数据也可能被压缩, 但 varlena 头部不会告诉你是否发生了压缩,这一点要由 TOAST 指针的内容来说明。

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

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

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

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

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

  • PLAIN 既不允许压缩,也不允许线外存储;此外,它还会禁止 varlena 类型使用单字节头部。 这是不可 TOAST 数据类型列唯一可能的策略。

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

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

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

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

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

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。