PostgreSQL 的 TOAST 是什么?大字段存储有什么影响?
简化版
TOAST 是 PostgreSQL 用来处理超大行和大字段的机制,会把过大的变长字段压缩或拆到旁边的 TOAST 表中存储。它让一行能保存大文本、JSONB、bytea,但大字段仍会增加 IO、缓存、Vacuum 和查询成本,列表查询应避免无谓读取大字段。
详细版
PostgreSQL 数据页通常是 8KB,一行太大时不能完整放入普通页。TOAST 会对变长字段进行压缩或外置存储,比如 text、jsonb、bytea。
注意点:
- TOAST 让大字段能存,不代表适合频繁读写。
- 查询不需要大字段时,不要
select *。 - 大 JSONB 更新可能导致整值重写。
- 大字段会影响表膨胀、Vacuum 和备份。
- 图片、视频等文件多数情况下仍应放对象存储。
面试回答要区分:TOAST 是数据库内部存储优化,不是万能的大文件方案。
完整版教学
一、TOAST 解决的是行太大的问题
PostgreSQL 默认数据页大小常见为 8KB,如果一行包含很长的 text 或 jsonb,普通页面放不下。TOAST 就是为这种超大变长字段设计的机制。
它会尝试压缩字段,压缩后仍然太大时,会把字段拆成块放到 TOAST 表里,主表行里保存引用。
主表行: id, title, content_pointer
TOAST 表: content chunk 1, chunk 2, chunk 3 ...
记忆钩子:TOAST 让大字段“能存下”,但不代表“大字段没有成本”。
二、哪些字段可能触发 TOAST
TOAST 主要作用于可变长度字段,比如 text、varchar、jsonb、bytea、数组等。短字段不会触发外置存储。
如果一篇文章正文 100KB,它很可能被压缩或拆到 TOAST 表;如果只是 100 字节标题,仍然在主表行内。
| 字段类型 | 是否可能 TOAST | 常见场景 |
|---|---|---|
text | 可能 | 文章正文、备注 |
jsonb | 可能 | 扩展属性、配置 |
bytea | 可能 | 二进制内容 |
int | 不会 | 普通数值 |
所以设计表时要识别哪些字段是冷的大字段,避免它们影响热查询。
三、select 星号会放大大字段成本
如果查询只需要标题和状态,却写 select *,数据库可能需要取出大字段引用,甚至访问 TOAST 数据。即使某些场景会延迟取值,应用层传输和反序列化也可能被拖慢。
-- 列表页推荐
select id, title, status, created_at
from article
where status = 'published';
假设列表一次查 20 行,每行正文 50KB,误查正文就会多传约 1MB 数据。高并发下这不是小问题。
四、大 JSONB 更新可能很贵
JSONB 很灵活,但如果一个大 JSONB 字段里只改一个小属性,底层仍可能产生新的行版本和新的 TOAST 数据。PostgreSQL 的 MVCC 更新是生成新版本,不是在原地随便改几个字节。
extra 大小 200KB
只改 extra.color
可能仍产生新的行版本和 TOAST 写入
因此高频更新字段不要埋在大 JSONB 里。稳定且常更新、常查询的字段应该列化。
五、TOAST 会影响 Vacuum 和表膨胀
大字段更新会产生旧版本,TOAST 表也会产生需要清理的旧数据。如果长事务阻止 Vacuum 回收,主表和 TOAST 表都可能膨胀。
排查时不能只看主表大小,也要看相关 TOAST 表大小。一个看似不大的业务表,背后的 TOAST 表可能非常大。
select pg_size_pretty(pg_total_relation_size('article'));
这个函数看总大小,比只看主表 heap 更接近真实占用。
六、大文件仍然更适合对象存储
TOAST 支持 bytea,但图片、视频、PDF 等大文件通常不建议直接放数据库。数据库备份、复制、恢复、连接传输都会被大文件拖重。
更常见方案是对象存储保存文件内容,PostgreSQL 保存元数据和权限关系。
PostgreSQL: file_id, owner_id, storage_key, size, hash
对象存储: 真正文件内容
数据库擅长结构化数据和事务,对象存储擅长大对象存储和分发。
七、常见误区与追问
- 误区:有 TOAST 就可以放心把所有大文件放库里。 TOAST 是内部存储机制,不解决大文件传输、备份和分发成本。
- 误区:JSONB 改一个字段一定很轻。 大 JSONB 更新可能导致整值新版本和 TOAST 写入。
- 误区:表不大就没有大字段问题。 TOAST 表可能占用大量空间,要看总关系大小。
- 追问:为什么不要 select 星号? 可能把大字段带入查询路径,增加 IO、网络和反序列化成本。
- 追问:文章正文要不要拆表? 高频列表不需要正文时,拆成正文表能隔离热冷字段。
- 追问:如何查看表总大小? 使用
pg_total_relation_size查看包含索引和 TOAST 的总大小。
八、面试中可以这样落地
如果设计文章表,可以主表保存标题、摘要、状态和时间,正文单独放正文表,列表页不查正文。
create table article (
id bigint primary key,
title text not null,
summary text not null,
status text not null
);
create table article_body (
article_id bigint primary key,
content text not null
);
如果是附件系统,则文件内容放对象存储,PostgreSQL 只存元数据。这样能把 TOAST 和对象存储的边界讲清楚。
九、加强记忆
TOAST 记住“压缩、外置、分块、仍有成本”。它解决大字段放不下的问题,不代表大字段读写便宜。面试时围绕 select *、JSONB 更新、Vacuum 膨胀和对象存储边界来回答,层次会很完整。