pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
本节概述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 字节,与所表示值的实际大小无关。
TOAST 代码只有在要存入表中的行值宽于 BLCKSZ/4 字节(通常为 2Kb)时才会被触发。 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 以内以便放得下。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。