PostgreSQL toast 存储技术

1. 为什么需要 toast 技术

PostgreSQL 表数据的存储以页作为基本单位,默认情况下,一个页面 8kb 大小,pg 不允许一条记录跨多个页面,那么当一条记录的大小超过 8kb 时怎么处理?这就需要引入 toast 技术,英文全称为 The OverSized Attribute Storage Technique(超尺寸字段存储技术)。主要思路是使用额外的 toast 表来存储大字段数据,在原来的字段存储区内存储对应的 toast 表数据的 id。

2. 特定的字段类型才能使用 toast 机制

\d+ table_name

可以查看表字段的 Storage,分为如下几种类型:

  • plain:避免压缩或行外存储
  • main:允许压缩,不允许行外存储
  • external:允许行外存储,不允许压缩
  • extended:允许压缩和行外存储

对于可变长度的字段,其 Storage 通常为 extended 或者 external,可变长度的字段可通过 toast 机制存储大字段类型。

3. 普通表与 toast 表的关联

查询 pg_class 表,其 reltoastrelid 就是 toast 表的 oid,toast 表的表名后缀为普通表的 oid,举个例子:

普通表 t,oid 为 10022077,reltoastrelid 为 10022080

toast 表为:pg_toast_10022077,oid 为 10022080。

普通表与 toast 表在 pg_class 中通过字段 relkind 与可以区别,普通表为 'r',toast 表为 't'

4. 普通表的大字段 与 toast 表记录关联

  • external 的字段数据的第 1 个字节必须为 0x01
  • extended 的字段数据的第 1 个字节的低 2 位不能全部为 0,低 1 位表示是否 short 存储,低 2 位表示是否可压缩

源码参考宏定义:

  • VARATT_IS_EXTERNAL
  • VARATT_IS_EXTENDED

对于使用 toast 存储的字段,通常使用宏 VARATT_IS_EXTERNAL_ONDISK 来对字段数据进行判断,即第 1 个字节为 0x01,第 2 个字节为 0x12,后面是一个 varatt_external 结构,如下:

typedef struct varatt_external
{
	int32		va_rawsize;		/* Original data size (includes header) */
	int32		va_extsize;		/* External saved size (doesn't) */
	Oid			va_valueid;		/* Unique ID of value within TOAST table */
	Oid			va_toastrelid;	/* RelID of TOAST table containing it */
}			varatt_external;

以上结构可以看出,一个 toast 字段,其存储的内容包括原始数据大小,外部数据大小,toast 表中数据的 chunk_id,toast 表的 oid。通过 oid,chunk_id,就能很容易的定位到字段数据。

toast 表中的数据通常是压缩的,判断是否压缩的方法是对比 va_rawsize 和 va_extsize 的大小,如下:

#define VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer) \
	((toast_pointer).va_extsize < (toast_pointer).va_rawsize - VARHDRSZ)

对于 toast 表,其单条记录也是有大小限制的,对于一个大字段,其在 toast 表中会对应多条记录,这些记录的 chunk_id 相同,但是 chunk_seq 不同。

看一个 toast 例子:
下面是一个普通表大字段存储的值:

\x0112191800002d0d0000c3ec9800c0ec9800

  • 0x0112 表示外部存储,即宏 VARATT_IS_EXTERNAL_ONDISK 对应的判断。
  • 0x19180000 表示 4 字节原始存储大小
  • 0x2d0d0000 表示 4 字节外部存储大小
  • 0xc3ec9800 表示 toast 表记录的 chunk_id
  • 0xc0ec9800 表示 toast 表的 oid

5. 涉及 toast 表的 DML 操作

对一个涉及大字段的普通表执行 DML 操作,普通表与 toast 表的 wal 写入顺序如下:

  • insert 操作,先 insert toast 表记录,再对普通表进行 insert 操作
  • update 操作,先 insert toast 表记录,再删除 toast 表记录,最后更新普通表。
  • delete 操作,先删除普通表记录,再删除 toast 表记录

以上可以看出,insert 与 update 都是先对 toast 表进行操作,然后对普通表操作。而 delete 则相反。


内核版本:pg12

文章评论

0条评论