网站域名 设置遂宁市住房和城乡建设局网站
2026/5/21 13:57:22 网站建设 项目流程
网站域名 设置,遂宁市住房和城乡建设局网站,建网站网站,网站建设方案.doc文章目录 一、传统锁模型 vs MVCC#xff1a;为什么需要多版本#xff1f;1.1 传统锁模型的局限1.2 MVCC 的核心思想1.3 PostgreSQL 中 MVCC 的实现基础#xff1a;元组头#xff08;HeapTupleHeader#xff09;1.4 事务快照#xff08;Snapshot#xff09;#xff1a;…文章目录一、传统锁模型 vs MVCC为什么需要多版本1.1 传统锁模型的局限1.2 MVCC 的核心思想1.3 PostgreSQL 中 MVCC 的实现基础元组头HeapTupleHeader1.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 Tuple4.2 VACUUMMVCC 的“清道夫”五、高级优化HOTHeap-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 之所以能在高并发场景下保持优异的性能和一致性其核心秘密之一就是MVCCMulti-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 的事务 ID0 表示未删除*/CommandId t_cid;/* 命令 ID用于同一事务内的多条语句区分*/...uint16 t_infomask;/* 标志位如 HEAP_XMIN_COMMITTED, HEAP_XMAX_INVALID 等 */...}HeapTupleHeaderData;这些字段是 MVCC 可见性判断的核心依据。关键字段解释字段含义t_xmin创建此元组的事务 IDt_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 106xip [103, 105]这意味着事务 102 及之前的修改可见事务 103 和 105 的修改不可见即使它们修改了数据事务 106 及之后的修改尚未发生1.5 实践建议避免长事务设置idle_in_transaction_session_timeout合理配置 autovacuum对高频更新表调低scale_factor监控表膨胀使用pg_bloat_check或查询pgstattuple使用连接池减少短连接带来的事务开销谨慎使用SERIALIZABLE虽然安全但可能频繁回滚定期 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_xmin在S.xip中活跃事务→不可见未提交步骤 2检查t_xmax删除者如果t_xmax 0→ 未被删除 →可见如果t_xmax S.xmax→ 删除发生在事务启动后 →仍可见如果t_xmax S.xmin→ 删除者已结束若已提交 →不可见若已回滚 →可见如果t_xmax在S.xip中 →可见因为删除未提交 这套逻辑确保了只有已提交且在快照“之前”完成的修改才可见。2.2 举例说明事务操作t_xmint_xmaxT1 (ID100)INSERT row A1000T2 (ID101)UPDATE row A → B100101INSERT row B1010事务 102快照 xmin102, xmax102看到 row A→t_xmin100 102但t_xmax101 102且已提交 →不可见看到 row B→t_xmin101 102且已提交 →可见事务 100 自己在 UPDATE 后能看到自己刚插入的 B 吗能因为t_xmin101是自己且在同一事务中通过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 使用SSISerializable Snapshot Isolation算法检测冲突并回滚。四、MVCC 的代价死元组与表膨胀MVCC 并非免费午餐。其最大代价是旧版本不会立即删除导致存储膨胀。4.1 什么是死元组Dead Tuple当一个元组满足以下条件时被称为“死元组”t_xmin对所有活跃事务都不可见即创建者已提交但被后续更新/删除t_xmax已提交即删除操作已完成这些元组不再被任何事务需要但仍然占据磁盘空间。4.2 VACUUMMVCC 的“清道夫”PostgreSQL 通过VACUUM进程回收死元组普通 VACUUM标记死元组为空闲空间供后续 INSERT 重用不释放磁盘VACUUM FULL重建表真正释放空间但会锁表不推荐在线使用autovacuum守护进程会自动触发 VACUUM基于以下阈值清理触发条件 autovacuum_vacuum_threshold autovacuum_vacuum_scale_factor * 表行数若 autovacuum 跟不上写入速度表会持续膨胀I/O 和查询性能下降。五、高级优化HOTHeap-Only Tuple更新为了减少索引更新开销和版本链长度PostgreSQL 引入了HOTHeap-Only Tuple技术。5.1 HOT 的前提更新的列不在任何索引中新元组可以放入同一个数据页5.2 HOT 的效果新元组不创建新的索引项通过页内指针链接旧元组 → 新元组查询时沿 HOT 链遍历直到找到可见版本减少 WAL 日志量减少索引维护开销加速 UPDATE 性能5.3 MVCC 与 WAL、Checkpoint 的协同MVCC 与 WALWrite-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 FULL、CLUSTER或pg_repack能真正释放空间6.3 误区 3“长事务只是占用连接”长事务尤其是idle in transaction会阻止 VACUUM 清理死元组导致表无限膨胀极端情况下可能触发事务 ID 回卷Wraparound使数据库进入只读模式

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询