news 2026/4/18 7:12:57

PostgreSQL 核心原理:读不阻塞写,写不阻塞读的秘密

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostgreSQL 核心原理:读不阻塞写,写不阻塞读的秘密

文章目录

    • 一、传统锁模型 vs MVCC:为什么需要多版本?
      • 1.1 传统锁模型的局限
      • 1.2 MVCC 的核心思想
      • 1.3 PostgreSQL 中 MVCC 的实现基础:元组头(HeapTupleHeader)
      • 1.4 事务快照(Snapshot):决定“你能看到什么”
      • 1.5 实践建议
    • 二、可见性判断:谁能看到这条记录?
      • 2.1 判断逻辑(简化版):
        • 步骤 1:检查 `t_xmin`(创建者)
        • 步骤 2:检查 `t_xmax`(删除者)
      • 2.2 举例说明
    • 三、解决“幻读”与“不可重复读”:隔离级别的实现
      • 3.1 Read Committed(读已提交)
      • 3.2 Repeatable Read(可重复读)
    • 四、MVCC 的代价:死元组与表膨胀
      • 4.1 什么是死元组(Dead Tuple)?
      • 4.2 VACUUM:MVCC 的“清道夫”
    • 五、高级优化:HOT(Heap-Only Tuple)更新
      • 5.1 HOT 的前提:
      • 5.2 HOT 的效果:
      • 5.3 MVCC 与 WAL、Checkpoint 的协同
    • 六、常见误区澄清
      • 6.1 误区 1:“MVCC 完全无锁”
      • 6.2 误区 2:“VACUUM 会立即释放磁盘空间”
      • 6.3 误区 3:“长事务只是占用连接”

PostgreSQL 之所以能在高并发场景下保持优异的性能和一致性,其核心秘密之一就是MVCC(Multi-Version Concurrency Control,多版本并发控制)。正是 MVCC 机制,使得 PostgreSQL 实现了“读不阻塞写、写不阻塞读”的理想并发模型——这是许多传统数据库(如 MySQL 的 MyISAM 或早期 Oracle)难以企及的能力。

本文将深入 PostgreSQL 的底层实现,从数据结构、事务可见性、元组版本链、快照机制到垃圾回收,全面解析 MVCC 如何工作,并揭示其背后的精巧设计与潜在代价。


一、传统锁模型 vs MVCC:为什么需要多版本?

PostgreSQL 的 MVCC 机制是其高并发能力的基石。它通过“保留历史版本 + 快照隔离”的方式,巧妙地实现了读写不互斥,同时保证 ACID 特性。然而,这种优雅的设计也带来了存储管理和维护的复杂性。

理解 MVCC 不仅有助于排查性能问题(如卡顿、膨胀),更能指导我们写出更高效的数据库应用。

1.1 传统锁模型的局限

在没有 MVCC 的数据库中(如使用两阶段锁 2PL),为了保证事务隔离性,通常采用以下策略:

  • 读操作:加共享锁(S 锁)
  • 写操作:加排他锁(X 锁)

这导致:

  • 写事务会阻塞所有读事务(直到提交)
  • 读事务会阻塞写事务(若持有 S 锁)

例如:一个长时间运行的SELECT查询会阻止其他会话对同一行执行UPDATE,造成严重的并发瓶颈。

1.2 MVCC 的核心思想

MVCC 的基本理念是:不覆盖旧数据,而是保留多个版本。每个事务看到的是它“应该看到”的那个版本,而不是最新版本。

读操作永远不需要加锁(只读快照)
写操作只需写入新版本,不影响正在读旧版本的事务

这种“时间旅行”式的视图,让读写完全解耦。


1.3 PostgreSQL 中 MVCC 的实现基础:元组头(HeapTupleHeader)

在 PostgreSQL 中,每一行数据(称为tuple)都存储在堆表(heap table)中,其物理结构包含一个关键部分:元组头(HeapTupleHeaderData)

typedefstructHeapTupleHeaderData{TransactionId t_xmin;/* 插入该 tuple 的事务 ID */TransactionId t_xmax;/* 删除/更新该 tuple 的事务 ID(0 表示未删除)*/CommandId t_cid;/* 命令 ID(用于同一事务内的多条语句区分)*/...uint16 t_infomask;/* 标志位:如 HEAP_XMIN_COMMITTED, HEAP_XMAX_INVALID 等 */...}HeapTupleHeaderData;

这些字段是 MVCC 可见性判断的核心依据。关键字段解释:

字段含义
t_xmin创建此元组的事务 ID
t_xmax删除或更新此元组的事务 ID(若为 0,表示未被删除)
t_cid同一事务内命令的序号(解决“自更新不可见”问题)
t_infomask优化标志位,缓存事务状态(如是否已提交)

注意:UPDATE在 PostgreSQL 中实际上是DELETE + INSERT—— 旧元组被标记为删除(t_xmax设为当前事务 ID),新元组被插入(t_xmin为当前事务 ID)。


1.4 事务快照(Snapshot):决定“你能看到什么”

每个事务在启动时(或首次访问数据时,取决于隔离级别)会获取一个事务快照(Snapshot)。这个快照定义了该事务在整个生命周期中“可见的数据世界”。

快照的组成(SnapshotData结构):

typedefstructSnapshotData{TransactionId xmin;// 最小活跃事务 ID(小于它的事务都已提交或回滚)TransactionId xmax;// 下一个将分配的事务 ID(大于等于它的事务尚未开始)TransactionId*xip;// 当前活跃事务 ID 列表(数组)uint32 xcnt;// 活跃事务数量...}

举个例子:

假设当前事务 ID 分配情况如下:

  • 已提交事务:100, 101, 102
  • 活跃事务:103(正在运行), 105(刚启动)
  • 下一个事务 ID 将是 106

那么一个在事务 104 中获取的快照可能是:

  • xmin = 103(因为 103 是最小的活跃事务)
  • xmax = 106
  • xip = [103, 105]

这意味着:

  • 事务 102 及之前的修改可见
  • 事务 103 和 105 的修改不可见(即使它们修改了数据)
  • 事务 106 及之后的修改尚未发生

1.5 实践建议

  1. 避免长事务:设置idle_in_transaction_session_timeout
  2. 合理配置 autovacuum:对高频更新表调低scale_factor
  3. 监控表膨胀:使用pg_bloat_check或查询pgstattuple
  4. 使用连接池:减少短连接带来的事务开销
  5. 谨慎使用SERIALIZABLE:虽然安全,但可能频繁回滚
  6. 定期 ANALYZE:确保统计信息准确,优化器选择高效计划

二、可见性判断:谁能看到这条记录?

PostgreSQL 使用函数HeapTupleSatisfiesVisibility()(实际由HeapTupleSatisfiesMVCC等实现)来判断某条元组对当前事务是否可见。

2.1 判断逻辑(简化版):

给定一个元组 T 和当前事务快照 S,判断 T 是否可见:

步骤 1:检查t_xmin(创建者)
  • 如果t_xmin >= S.xmax→ 元组在事务启动后才创建 →不可见
  • 如果t_xmin < S.xmin→ 创建者已结束:
    • t_xmin已提交 →可能可见
    • t_xmin已回滚 →不可见
  • 如果t_xminS.xip中(活跃事务)→不可见(未提交)
步骤 2:检查t_xmax(删除者)
  • 如果t_xmax == 0→ 未被删除 →可见
  • 如果t_xmax >= S.xmax→ 删除发生在事务启动后 →仍可见
  • 如果t_xmax < S.xmin→ 删除者已结束:
    • 若已提交 →不可见
    • 若已回滚 →可见
  • 如果t_xmaxS.xip中 →可见(因为删除未提交)

💡 这套逻辑确保了:只有已提交且在快照“之前”完成的修改才可见

2.2 举例说明

事务操作t_xmint_xmax
T1 (ID=100)INSERT row A1000
T2 (ID=101)UPDATE row A → B100101
INSERT row B1010
  • 事务 102(快照 xmin=102, xmax=102):

    • 看到 row A?→t_xmin=100 < 102,但t_xmax=101 < 102且已提交 →不可见
    • 看到 row B?→t_xmin=101 < 102且已提交 →可见
  • 事务 100 自己(在 UPDATE 后):

    • 能看到自己刚插入的 B 吗?能!因为t_xmin=101是自己,且在同一事务中通过t_cid区分命令顺序。

三、解决“幻读”与“不可重复读”:隔离级别的实现

PostgreSQL 支持四种 SQL 标准隔离级别,其中Read Committed(默认)Repeatable Read / Serializable的实现都依赖 MVCC。

3.1 Read Committed(读已提交)

  • 每次 SQL 语句执行时获取新快照
  • 同一事务中,两次SELECT可能看到不同结果(因为中间有其他事务提交)

3.2 Repeatable Read(可重复读)

  • 事务开始时获取一次快照,全程复用
  • 所有查询看到一致的数据视图

避免脏读、不可重复读
避免幻读(PostgreSQL 通过 MVCC + SSI 实现)

注意:PostgreSQL 的 Repeatable Read 实际上达到了 SQL 标准的Serializable级别(除了极少数边界情况),而真正的 Serializable 使用SSI(Serializable Snapshot Isolation)算法检测冲突并回滚。


四、MVCC 的代价:死元组与表膨胀

MVCC 并非免费午餐。其最大代价是:旧版本不会立即删除,导致存储膨胀

4.1 什么是死元组(Dead Tuple)?

当一个元组满足以下条件时,被称为“死元组”:

  • t_xmin对所有活跃事务都不可见(即创建者已提交,但被后续更新/删除)
  • t_xmax已提交(即删除操作已完成)

这些元组不再被任何事务需要,但仍然占据磁盘空间。

4.2 VACUUM:MVCC 的“清道夫”

PostgreSQL 通过VACUUM进程回收死元组:

  • 普通 VACUUM:标记死元组为空闲空间,供后续 INSERT 重用(不释放磁盘)
  • VACUUM FULL:重建表,真正释放空间(但会锁表,不推荐在线使用)

autovacuum守护进程会自动触发 VACUUM,基于以下阈值:

清理触发条件 = autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor * 表行数

若 autovacuum 跟不上写入速度,表会持续膨胀,I/O 和查询性能下降。


五、高级优化:HOT(Heap-Only Tuple)更新

为了减少索引更新开销和版本链长度,PostgreSQL 引入了HOT(Heap-Only Tuple)技术。

5.1 HOT 的前提:

  • 更新的列不在任何索引中
  • 新元组可以放入同一个数据页

5.2 HOT 的效果:

  • 新元组不创建新的索引项
  • 通过页内指针链接旧元组 → 新元组
  • 查询时沿 HOT 链遍历,直到找到可见版本

减少 WAL 日志量
减少索引维护开销
加速 UPDATE 性能

5.3 MVCC 与 WAL、Checkpoint 的协同

MVCC 与 WAL(Write-Ahead Logging)紧密配合:

  • 所有元组修改(包括t_xmin/t_xmax设置)都记录 WAL
  • 崩溃恢复时,通过 WAL 重放,重建正确的元组状态
  • Checkpoint 确保脏页刷盘,但不会影响 MVCC 可见性逻辑

此外,WAL 日志本身也包含事务提交/回滚记录,用于在恢复时判断t_xmin/t_xmax的最终状态。


六、常见误区澄清

6.1 误区 1:“MVCC 完全无锁”

  • 虽然读不加锁,但写操作仍需加轻量级锁(LWLock)保护共享结构
  • DDL(如ALTER TABLE)仍会加表级排他锁
  • 行级冲突(如两个事务同时 UPDATE 同一行)会导致一个等待

6.2 误区 2:“VACUUM 会立即释放磁盘空间”

  • 普通 VACUUM 只标记空间可重用,不会缩小表文件
  • 只有VACUUM FULLCLUSTERpg_repack能真正释放空间

6.3 误区 3:“长事务只是占用连接”

  • 长事务(尤其是idle in transaction)会阻止 VACUUM 清理死元组,导致表无限膨胀
  • 极端情况下可能触发事务 ID 回卷(Wraparound),使数据库进入只读模式

    版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
    网站建设 2026/4/13 14:37:41

    交互装置在2026展厅展馆中的常见展项

    在当代展厅和展馆中&#xff0c;交互装置已经成为不可或缺的重要组成部分。这些装置不仅提升了观众的参观体验&#xff0c;还增强了展览内容的传达效果和记忆点。从多种多样的互动展项中&#xff0c;我们可以看到交互装置在展厅展馆中的多方面重要性。 交互装置极大地提升了观众…

    作者头像 李华
    网站建设 2026/4/18 5:17:22

    <span class=“js_title_inner“>从数据供给到价值变现的闭环构建|大模型与数据要素论坛圆满落幕!</span>

    汇聚来自产学研各界的顶级专家与企业领袖&#xff0c;共同探讨如何通过数据采集、标注、生产、评估、交易流通等全链路环节&#xff0c;构建“行业数据模型”的AI产品闭环&#xff0c;推动新质生产力蓬勃发展。 在大模型时代&#xff0c;数据已成为模型发展重要要素。近日&…

    作者头像 李华
    网站建设 2026/4/18 6:38:17

    百考通「降重+降AI」双模优化功能:一键化解查重与AI检测双重压力

    面对日益严格的学术审核机制&#xff0c;当代大学生不仅要应对传统查重系统的文字重复率要求&#xff0c;还需警惕各类AI内容检测工具对“生成痕迹”的识别。许多学生即便独立完成论文&#xff0c;也可能因语言风格过于规范、逻辑结构高度清晰而被误判为AI代写&#xff1b;而借…

    作者头像 李华
    网站建设 2026/4/18 6:39:40

    2026年了,你还在用传统滚动监听做懒加载?试试这种现代方案

    前言 当我们进入某个包含大量图片的网站时&#xff0c;网页内的图片却迟迟加载不出来&#xff0c;给我们带来了极差的用户体验&#xff0c;而懒加载技术&#xff0c;正是解决这类问题的有效手段之一。 传统的滚动监听懒加载方式在过去发挥了重要作用&#xff0c;但随着技术的不…

    作者头像 李华