如果你写过后端代码,Postgres 大概率在你的技术栈里。但一个尴尬的事实是:绝大多数工程师和数据库的关系,停留在“会用 SQL”的层面——至于一行数据INSERT进去之后物理上发生了什么、为什么大表UPDATE会让磁盘暴涨、为什么一个忘了提交的事务能拖垮整库——全靠猜。
2026年8月13日,后端工程领域知名教育者 Hussein Nasser 上架了一期视频:《you won't forget how postgres works after this》(看完这期你就忘不掉 Postgres 的工作原理了)。标题很狂,但数据给了底气:发布仅两周播放量约9.7万(对垂类数据库内核内容来说,这个速度相当快),视频时长26分57秒,是他"Intermediate and Advanced Backend Engineering Course"系列的白板课第一部分。
这期视频的价值在于:它不讲语法、不讲调优参数,而是回到存储的最小物理单元——Page(页)、Heap(堆)、Tuple(元组),把数据库从“黑盒软件”还原成“有空间结构的数据仓库”。你一旦在脑子里建立起那张“8KB页面”的图,很多以前死记硬背的结论(比如“Postgres 更新就是追加新行”)会瞬间变成“显然如此”。
更难得的是,Hussein 是白板教学——不念幻灯片,而是当着你的面把结构画出来。这种教学法本身就值得所有技术内容创作者学习。下面我们逐层拆解。
| 项目 | 内容 |
|---|---|
| 视频标题 | you won't forget how postgres works after this |
| 频道 | Hussein Nasser(@hnasr,后端工程领域头部技术频道) |
| 发布日期 | 2026年8月13日 |
| 时长 | 26分57秒 |
| 播放量 | 约9.7万(发布两周内,据YouTube搜索页数据) |
| 内容形式 | 白板课(Whiteboard lesson),中级/高级后端工程课程系列 Part 1 |
| 核心主题 | PostgreSQL 存储架构基础:pages、heaps、tuples 及其在数据操作、存储与事务管理中的交互 |
视频的主线,是把 Postgres 的存储世界还原成三层结构:Database → Heap(表文件)→ Page(页)→ Tuple(元组)。
你的一张表,在磁盘上并不是 Excel 那样的二维表格,而是若干个 8KB 大小的 Page 串成的集合。Page 是 Postgres 读写磁盘的最小单位——哪怕你只想改一行 100 字节的数据,Postgres 也要把这一行所在的整个 8KB 页面读进内存,改完再写回去。
我的延伸分析:为什么偏偏是 8KB?这与文件系统块的典型大小(4KB)有关——8KB 是文件系统块的整数倍,避免了“读一个逻辑页要碰两次磁盘块”的碎片问题;同时它又足够小,不至于让一次单行修改就搬运几MB的垃圾数据。这个数字是 Postgres 编译期固定常量(BLCKSZ),默认不能在线修改。理解“Page 是 IO 的原子单位”后,你会明白一个反直觉的结论:行再小,写放大也是 8KB 起。这也是为什么超宽表(几百列)和频繁单行更新在小机器上是灾难。
视频第二个关键概念是 Heap。Postgres 的表在术语里叫 "heap table"——堆表。这个名字本身就在提醒你:数据在物理上是“堆”进去的,不保证任何顺序。
页面用链表式的结构串联,新行写入时通常追加到还有空间的页面里。表里没有“第几行”的概念——每一行(Tuple)真正的身份证是 CTID,即 (页号, 页内偏移) 的物理坐标。
我的延伸分析:这一点直接解释了两个高频面试题和两个生产事故:
- 为什么 ORDER BY 需要排序步骤? 因为堆里数据本来就是乱的;
- 为什么没有索引的 WHERE 查询是全表扫描? 因为数据库只能从第 0 页开始一页页翻,像在没有目录的书里找一句话;
- CTID 为什么不能当主键? 因为下一节要讲的 MVCC 机制会让行的物理位置随时变化——VACUUM 收缩页面后 CTID 就变了。
视频用白板画出这个“页串”的瞬间,比读十遍文档都直观——这正是标题里 "you won't forget" 的底气所在。
这是本视频最有“含金量”的部分。每个 Tuple 不只是你的业务数据,前面还挂着一个行头(Tuple Header),里面藏着几个对用户不可见的字段——最核心的是 xmin(创建该行版本的事务ID)和 xmax(删除/作废该行版本的事务ID)。
这套设计是 Postgres MVCC(多版本并发控制)的物理基础:Postgres 的 UPDATE 从不原地修改数据,而是写入一个新版本的 Tuple,再把旧版本的 xmax 打上标记;DELETE 也只是打标记,不立刻释放空间。
我的延伸分析:这个设计是一次深刻的取舍—— - 换来的:读写互不阻塞。读旧版本的快照,写新版本,二者并行,这就是 Postgres 高并发的秘密; - 付出的:旧版本 Tuple(Dead Tuple,死元组)不会自动消失,必须靠 VACUUM 进程事后清扫。清扫不及时,表就会“膨胀”(Table Bloat)——表文件里装着大量已死但还占着 8KB 页面空间的数据。
一句话记住它:在 Postgres 里,UPDATE = INSERT + 给旧数据贴封条,VACUUM 是清道夫。 这一条如果只能记住一件事,就是它。
视频的高明之处在于:它不是孤立地讲结构,而是让结构解释现象。理解 Page/Heap/Tuple 之后,以下三个工程界的“玄学”瞬间变成必然(此节为我的延伸映射,逻辑与视频主线一致):
玄学一:为什么长事务是数据库大敌? 因为 VACUUM 不能清理“仍可能被某个事务看见”的死元组。一个开了三天没提交的事务,等于让全库的死元组堆积三天——磁盘暴涨、查询变慢,而罪魁祸首可能只是一个忘了 commit 的调试会话。
玄学二:为什么 COUNT(*) 在 Postgres 里慢?
因为“一行是否存在”取决于 MVCC 可见性判断——每个后端进程要根据自己的事务快照逐行判断可见性,没有任何缓存能告诉你一个全局精确的行数。这是 MVCC 的代价,不是优化不够。
玄学三:为什么删了数据磁盘不变小? 因为 DELETE 只是打标记,页面里的空间被标记为可复用,但文件本身不收缩(除非用 VACUUM FULL 这种锁表的重型操作)。
视频系列围绕页面与元组展开,这里补充一个与之直接相关的机制——TOAST(The Oversized-Attribute Storage Technique)。Postgres 页面是 8KB,而单个 Tuple 理论上必须能装进一个页面。那 text、jsonb 这种可能几MB的大字段怎么办?
Postgres 的策略是两步:先压缩,压不下再行外存储——把大字段切到一张看不见的关联表(TOAST 表)里,主行里只留一个指针。大字段独立成链,读取时按需取出,还能利用页面不装大字段这一点让数据页更“密”。
实战意义:当你的 jsonb 字段动辄几百KB时,每一次 SELECT * 都可能触发 TOAST 解压与重组——这就是“宽 JSONB 表查询慢”的底层原因。
页面结构讲清楚了,还差最后一块拼图:崩溃了怎么办?Postgres 的答案是 WAL(Write-Ahead Log,预写日志)——任何对页面的修改,在落盘之前必须先把“我改了什么”写入 WAL 并持久化。
崩溃恢复时,数据库从上一次检查点(Checkpoint)开始重放 WAL,把页面“重做”到一致状态。它和 Page/Tuple 的关系是:WAL 记录的是对页面的物理级变更,页面是变更的承受者,检查点是两者同步的里程碑。 Hussein 频道更早的一期《PostgreSQL Internal Architecture Explained》(21.2万播放)把 WAL、Checkpointer、Autovacuum 等后台进程体系讲得更系统,适合作为本片的“续集”。
最后值得单独说的是这期视频的形式本身。Hussein 全程白板手绘,没有一页 PPT。当“页面→元组→行头”的空间关系被画在同一块板面上,你记住的不是知识点,而是一张空间地图——这正是认知科学里“空间记忆优于线性文本记忆”的经典应用。
对技术内容创作者的启示:讲底层机制,最高效的载体不是动画炫技,而是一块板、一支笔、一条从存储介质到内存的完整推理链。这也是这条视频两周近10万播放的传播学解释:它提供的不是信息,而是心智模型。
flowchart TD
A[Postgres 存储世界] --> B[Heap 堆表文件]
B --> C[Page 页 8KB / IO最小单位]
C --> D[Tuple 元组 = 行头 + 业务数据]
D --> D1[ xmin 创建事务ID ]
D --> D2[ xmax 作废事务ID ]
D1 & D2 --> E[MVCC 多版本并发控制]
E --> F[UPDATE = 新版本 + 旧版打标记]
F --> G[Dead Tuple 死元组]
G --> H[VACUUM 清道夫]
H --> H1[堆积 → 表膨胀]
C --> I[行超8KB → TOAST 行外存储]
C --> J[页面修改先写 WAL 预写日志]
J --> K[Checkpoint 检查点 / 崩溃恢复]
E --> L[可见性判断 → COUNT(*) 慢 / 长事务阻塞清理]
1. 把“长事务监控”提到最高优先级。 立刻检查你的生产库:SELECT pid, now()-xact_start AS dur, query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY dur DESC; 任何超过几分钟的未结束事务都值得警惕。连接池配置里给 idle_in_transaction_session_timeout 设一个上限,是成本最低的保险。
2. 学会用死元组比例判断表健康度。 SELECT relname, n_dead_tup, n_live_tup FROM pg_stat_user_tables; 当 n_dead_tup / n_live_tup 明显偏高(比如超过20%),说明 autovacuum 跟不上更新频率——高频 UPDATE 的表应单独调激进的 autovacuum 参数,而不是用全局默认值。
3. 批量更新学会“分批提交”。 一次 UPDATE 一千万行 = 一千万个死元组 + 一个巨大的长事务。改成每批5000行、批间提交,vacuum 才有机会跟上。
4. 宽 JSONB 表慎用 SELECT *。 明白 TOAST 机制后你会知道, SELECT * 可能把几个MB的 TOAST 数据拖出来。按需取列,或把大 JSON 拆成独立冷表。
5. 面试与学习的降维打击。 当面试官问“为什么 Postgres 不需要 VACUUM 的 MySQL(InnoDB)有 undo log?”,你能从“行头里的 xmin/xmax + 堆表不原地更新”一路讲到“回滚段 vs 死元组清理”的架构分野——这种从物理结构出发的回答,比背八股文高一个段位。
当你真正看见“一行数据 = 8KB页面里带着 xmin/xmax 出生证的一条记录”,Postgres 的所有怪脾气——膨胀、慢 COUNT、长事务之祸——都不再是需要背的结论,而是推一遍就懂的必然。
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
