一、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 一定很贵”

那现在可以修正为:

✔️ 只有“改大字段”才贵


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐