开封建网站的公司,盘多多网盘搜索,lnmp wordpress 后台没有主题,百度搜索广告价格前言
上篇博客讲述了 postgresql 如何存储变长数据#xff0c;它的应用主要是在 toast 。Toast 在存储大型数据时#xff0c;会将它存储在单独的表中#xff08;称为 toast 表#xff09;。因为 postgresql 的 tuple#xff08;行数据#xff09;是存在在 Page 中的它的应用主要是在 toast 。Toast 在存储大型数据时会将它存储在单独的表中称为 toast 表。因为 postgresql 的 tuple行数据是存在在 Page 中的Page 的大小默认为 8KB。postgresql 不允许 tuple 跨页存储所以当一行数据的某个列数据过大时比如 text 类型的数据超过了单页的大小那么 postgresql 会将它压缩切分并且存储在另外的位置。这种技术就是称为 Toast。
Toast 表
如果我们创建了一张表有了变长数据的列那么就会有一个对应的 toast 表专门存储过大的数据。下面展示了一个例子
test# \d mytableTable public.mytableColumn | Type | Collation | Nullable | Default
-----------------------------------------------id | integer | | not null | name | text | | |
Indexes:mytable_pkey PRIMARY KEY, btree (id)可以看见 mytable 有个变长数据类型的列 name。然后我们来看看表对应的 oid。
test# select oid from pg_class where relname mytable;oid
--------127295
(1 row)可以看到 mytable 表的 oid 为 127295那么可以推断出它的 toast 表名称为 pg_toast_127295其对应的 oid 仍然可以通过上述语句查出来在此省略了或者
test# select reltoastrelid from pg_class where relname mytable;reltoastrelid
---------------127298
(1 row)我们继续来看看 pg_toast_127295 表有哪些列
test# select attname, typname from pg_attribute inner join pg_type ON pg_type.oid pg_attribute.atttypid where pg_attribute.attrelid 127298;attname | typname
---------------------chunk_data | byteachunk_seq | int4chunk_id | oidtableoid | oidctid | tidxmin | xidxmax | xidcmin | cidcmax | cid
(9 rows)除去下面的通用属性它只定义了三列
切分数据 假设现在向 mytable 表插入一条大型数据长度为3MB里面存储了一张图片采用了base64格式。
INSERT INTO mytable (name) values (j7161gnb1u23 ...... 972bh6);postgresql 在处理这条请求时发现 name 是 text 类型并且这次插入的数据过大。那么首先它会被压缩假设被压缩成 1MB压缩后仍然不满足大小然后按照指定的大小默认为 2048 byte切分成 512 份。每一份切片对应 toast 表的一行数据它们的 chunk_id 都是相同的因为属于同一个数据只是 chunk_seq 不同对应着切片位置。chunk_data 列就是存储着切片的数据。
数据结构
下面的图展示了普通表的 heap 数据和 toast 表的数据联系。 toast 表的数据格式在上面已经讲解过了这里详细介绍了普通表的数据格式它存储了 toast 表的对应数据位置。首先它是 varattrib_1b_e 数据类型
typedef struct
{uint8 va_header; uint8 va_tag; /* 类型 */char va_data[FLEXIBLE_ARRAY_MEMBER];
} varattrib_1b_e;它的 va_tag 类型为 VARTAG_ONDISK 后面的 va_data数据格式如下
typedef struct varatt_external
{int32 va_rawsize; /* Original data size (includes header) */int32 va_extsize; /* External saved size (doesnt) */Oid va_valueid; /* Unique ID of value within TOAST table */Oid va_toastrelid; /* RelID of TOAST table containing it */
} varatt_external;src/backend/access/common/toast_internals.c文件中的toast_save_datum函数实现了如何将数据存储到 toast 表下面的程序经过简化处理了。
/*rel: 普通表value: 该列数据oldexternal: 用于更新用的
*/
Datum toast_save_datum(Relation rel, Datum value, struct varlena *oldexternal, int options) {struct varatt_external toast_pointer;Datum t_values[3];t_values[0] ObjectIdGetDatum(toast_pointer.va_valueid);t_values[2] PointerGetDatum(chunk_data);int32 chunk_seq 0; // 切片索引// 开始切分数据data_todo 是剩余数据的长度while (data_todo 0){int i;// 计算切片长度chunk_size Min(TOAST_MAX_CHUNK_SIZE, data_todo);// 记录 chunk_seq 列值t_values[1] Int32GetDatum(chunk_seq);// 记录切片数据, data_p 记录了写入的数据位置SET_VARSIZE(chunk_data, chunk_size VARHDRSZ);memcpy(VARDATA(chunk_data), data_p, chunk_size);// 插入 toast 表toasttup heap_form_tuple(toasttupDesc, t_values, t_isnull);heap_insert(toastrel, toasttup, mycid, options, NULL);// toast 表设置了 chunk_id 和 chunk_seq 的符合唯一索引for (i 0; i num_indexes; i){if (toastidxs[i]-rd_index-indisready)index_insert(toastidxs[i], t_values, t_isnull,(toasttup-t_self),toastrel,toastidxs[i]-rd_index-indisunique ?UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,NULL);}data_todo - chunk_size;data_p chunk_size;}}切片策略
上面展示了 postgresql 对变长数据的默认切片策略其实它还提供了别的策略。总共支持四种如下所示
PLAIN数据不能被压缩也不能存储到 toast 表 EXTENDED默认策略可以被压缩也可以存储到 toast 表 EXTERNAL不能被压缩但可以存储到 toast 表 MAIN可以被压缩也可以存储到 toast 表只不过它的优先级比EXTENDED低
技术比较
postgresql 并没有使用跨页存储的方案而是将大型数据单独放到其余地方存储。这样在条件过滤时会比较好因为它不需要读取这些大的数据而且只有当该列被选中时才会在返回数据时去读取。这种场景下减少了磁盘 IO 的读取提升了性能。
同样它也有对应的缺点那就是写入大型的数据时会比较慢。因为它需要切片然后插入到 toast 表中还要更新 toast 表的索引。如果采用跨页存储那么还可以利用磁盘顺序写的高性能。在读取整行数据时候还需要先去寻找 toast 表的索引然后再去读取 toast 表的数据相比较跨页存储仍然无法使用磁盘顺序读的高性能。 作者zhmin 链接https://zhmin.github.io/posts/postgresql-toast/ #PG证书#PG考试#postgresql培训#postgresql考试#postgresql认证