PostgreSQL 大字段更新的误区:TOAST 根本不会复制!(源码级深度解析)
一、99%的人都理解错了
很多人对 PostgreSQL 有一个“看似合理”的认知:
❓ 表里有个 1MB 的 JSONB 字段,每次 UPDATE 其他列,会不会复制这 1MB?
直觉答案通常是:
💥 会!因为 UPDATE 会生成新行
但真实答案是:
❗ 不会复制!甚至 TOAST 数据完全不动!
这背后,是 PostgreSQL 一个非常精妙的设计。
二、先说结论(建议先记住)
✔️ UPDATE 一定生成新 heap tuple
✔️ 但 TOAST 数据是“引用复用”的
✔️ 不更新该字段 → 不会重写 TOAST
三、TOAST 的本质(快速回顾)
当字段很大(通常 > 2KB):
heap 表:只存 pointer
toast 表:存真实数据(分 chunk)
结构类似:
heap row:
id | big_text_ptr
toast table:
chunk_id | chunk_seq | chunk_data
👉 heap 里只是一个“指针”。
四、关键问题:UPDATE 时发生了什么?
我们看这个 SQL:
UPDATE t SET status = 1 WHERE id = 1;
假设:
-
表里有一个
big_json jsonb(TOAST 字段) -
本次 UPDATE 没改它
五、真实发生的事情(非常重要)
5.1 heap 层
old tuple → new tuple(一定发生)
5.2 TOAST 层
old tuple: ptr → toast_data
new tuple: ptr → toast_data (完全一样)
👉 同一份 TOAST 数据,被两个版本共享!
六、为什么可以共享?会不会有问题?
这就是 PostgreSQL 设计最巧妙的地方。
🔥 核心原则
TOAST 数据是 immutable(不可变的)
👉 一旦写入:
-
不会原地修改
-
只能新建
✔️ 所以:
多个 tuple 可以安全共享:
old version → toast_data
new version → toast_data
不会出现数据污染。
七、源码是怎么做的?
关键函数在:
src/backend/access/heap/heaptoast.c
核心入口:
heap_toast_insert_or_update(...)
关键逻辑(简化版)
for each attribute:
if attribute unchanged:
reuse old toast pointer
else:
write new toast data
“是否变化”如何判断?
来自:
HeapDetermineColumnsInfo(...)
👉 在 heap_update 阶段已经算好了:
-
哪些列真的 changed
-
哪些可以直接复用
八、三种情况对比
✅ 情况1:不更新 TOAST 字段
UPDATE t SET small_col = 1;
结果:
-
✔️ 新 heap tuple
-
✔️ 复用 TOAST pointer
-
❌ 不写 TOAST
❗ 情况2:更新 TOAST 字段
UPDATE t SET big_json = '{"a":1}';
结果:
-
✔️ 新 heap tuple
-
✔️ 新 TOAST chunk
-
❌ 旧 TOAST 立即删除(不会!)
👉 旧数据等 VACUUM 清理
⚠️ 情况3:值“看起来一样”
UPDATE t SET big_json = big_json;
可能:
-
✔️ 识别为未变化 → 不重写
-
❗ 识别为变化 → 重写(少见)
九、HOT update 对 TOAST 的影响
很多人会问:
❓ HOT 和 TOAST 有关系吗?
答案是:
👉 几乎没有直接关系
HOT 只关心:
是否影响索引列
所以:
| 场景 | 是否复制 TOAST |
|---|---|
| HOT update | ❌ 不复制 |
| 普通 update | ❌ 不复制 |
| 修改 TOAST 字段 | ✔️ 才复制 |
十、一个非常隐蔽的性能杀手
💣 JSONB 高频更新
UPDATE t
SET data = jsonb_set(data, '{a}', '1');
你以为只是改一个 key,实际上:
💥 整个 JSONB 被重写 → 新 TOAST
后果:
-
WAL 暴涨
-
IO 暴涨
-
TOAST 表膨胀
-
VACUUM 压力巨大
十一、如何优化?
✅ 1. 拆字段(强烈推荐)
-- ❌
data jsonb
-- ✅
a int,
b text,
data jsonb(低频)
✅ 2. 避免频繁 UPDATE 大字段
改成:
append-only + 异步聚合
✅ 3. 调整 autovacuum
ALTER TABLE t SET (
autovacuum_vacuum_scale_factor = 0.01
);
✅ 4. 监控 TOAST 表
SELECT reltoastrelid::regclass
FROM pg_class
WHERE relname = 't';
十二、一句话总结
💡 PostgreSQL 的精妙之处在于:
👉 它复制的是“指针”,不是“大数据”本身
结尾
如果你之前以为:
“大字段 UPDATE 一定很贵”
那现在可以修正为:
✔️ 只有“改大字段”才贵
更多推荐


所有评论(0)