pgrust 快照管理:snapmgr 与 MVCC 可见性判断实现
【免费下载链接】pgrustPostgres rewritten in Rust, now faster than Postgres and Clickhouse项目地址: https://gitcode.com/GitHub_Trending/pg/pgrust
pgrust 是一个用 Rust 重写 PostgreSQL 的开源项目,它的目标不仅是"翻译" C 代码,更要在保证语义一致的前提下发挥 Rust 的内存安全优势。在众多底层机制中,快照(Snapshot)管理和MVCC 可见性判断是数据库并发控制的核心——它决定了"谁能看到哪一行数据"。本文面向初学者,用通俗的方式拆解 pgrust 中 snapmgr 模块与 MVCC 可见性判断的实现思路。
一、什么是数据库快照(Snapshot)
简单说,快照就是"某一时刻数据库的一致视图"。在 MVCC(多版本并发控制)模型下,读操作不会阻塞写操作,每个事务看到的都是自己启动时那一刻的数据状态。这个"状态"就是由快照来描述的。
在 pgrust 中,快照的核心数据结构SnapshotData定义在 snapshot.rs,它对应 PostgreSQL 的utils/snapshot.h。一个快照主要由以下字段构成:
| 字段 | 含义 | 类比 |
|---|---|---|
xmin | 所有小于 xmin 的事务都已提交 | "一定看得见的底线" |
xmax | 所有大于等于 xmax 的事务都不可见 | "一定看不见的上限" |
xip | 正在进行中的事务 ID 数组 | "灰色地带" |
subxip | 进行中的子事务 ID 数组 | "灰色地带的细分" |
curcid | 当前命令 ID | "我自己改到哪一步了" |
💡 记忆技巧:xmin 是"下界",xmax 是"上界",xip 是"中间那些还没下结论的"。
二、MVCC 可见性判断的核心原理
MVCC 的可见性判断,本质上就是回答一个问题:"这条元组(tuple)的插入事务和删除事务,对于当前快照来说,状态是什么?"
判断流程大致如下:
- 如果元组的插入事务 ID(xmin)小于当前快照的 xmin,说明它早已提交 →可见
- 如果插入事务 ID 大于等于快照的 xmax,说明它太"新" →不可见
- 如果插入事务 ID 落在 xip 列表中,说明该事务还在进行中 →不可见
- 只有当插入事务已提交、且删除事务未提交或不存在时,元组才可见
pgrust 把这段逻辑实现在 heapam_visibility 模块中,它对应 PostgreSQL 的heapam_visibility.c。这里值得一提的是,pgrust 用HTSV_Result枚举(如HEAPTUPLE_DEAD、HEAPTUPLE_LIVE、HEAPTUPLE_RECENTLY_DEAD)来表达真空(VACUUM)视角下的元组状态,判别值顺序与 C 枚举完全一致,保证了对齐迁移的兼容性。
三、pgrust 的 snapmgr 模块在哪里
快照的"生产、登记、销毁"都由snapmgr负责,它的实现在:
- 主要逻辑:crates/backend/utils/time/snapmgr/src/lib.rs
- 快照类型定义:crates/_support/types/snapshot/src/snapshot.rs
- 数据采集(对应 C 的
GetSnapshotData):crates/backend/storage/ipc/procarray/src/snapshot.rs
有意思的是,pgrust 的代码注释里明确标注了每个函数对应的 C 源文件,例如utils/time/snapmgr.c、utils/snapshot.h,这让熟悉 PostgreSQL 的开发者可以无缝对照阅读。
四、三种常用快照的获取方式
snapmgr 提供了多个入口函数,最常用的有以下三类:
1. 事务快照(GetTransactionSnapshot)
对应GetTransactionSnapshot(),在事务第一次执行查询时生成,整个事务期间保持不变。它保证了事务内读一致性——同一个事务里多次查询,看到的数据是一致的。
2. 活跃快照(GetActiveSnapshot)
对应GetActiveSnapshot(),返回当前压入"活跃快照栈"顶部的快照。事务快照生成后会被压栈,执行器通过它来读取数据。
3. 目录快照(GetCatalogSnapshot)
对应GetCatalogSnapshot(relid),专用于系统目录的读取,配合失效消息(invalidation)机制,保证 DDL 与系统表读操作的一致性。
五、快照的生命周期管理
快照不是"用完即弃"的,它需要严格的登记与释放机制,否则会带来内存泄漏或可见性错误。snapmgr 提供了两套配套机制:
活跃快照栈(Active Snapshot Stack)
PushActiveSnapshot:把快照压入栈顶PopActiveSnapshot:弹出栈顶快照- 栈式结构天然匹配"函数嵌套调用"的场景,比如 SPI 调用、触发器嵌套等
注册快照(Registered Snapshots)
RegisterSnapshot:登记一个快照,让它在事务期间"保活"UnregisterSnapshot:释放登记- pgrust 用
Vec扫描方式替代了 C 版本中的配对堆(pairing heap),代码更简洁,也更容易验证正确性
六、一个直观的执行流程
把上面的概念串起来,一次普通 SELECT 查询的快照流程大概是:
BEGIN └─ 第一次查询 ├─ GetTransactionSnapshot() 生成事务快照 ├─ PushActiveSnapshot() 压入活跃栈 ├─ 执行器按 xmin/xmax/xip 判断每行可见性 ├─ PopActiveSnapshot() 弹出快照 └─ 事务结束统一释放注册快照 COMMIT七、pgrust 实现上的几个亮点
- 所有权清晰:
SnapshotData中的xip、subxip用 Rust 的Vec管理,替代了 C 的裸指针数组,从根本上消除了越界风险。 - 语义保留:虽然换了数据结构,但
xcnt、subxcnt等计数字段仍然显式保留,保证字段级语义与 C 版本完全对齐。 - 渐进式迁移:项目通过大量"seam"(接缝)模块逐步替换 C 实现,例如
snapmgr内部仍保留elog_error等错误处理辅助函数,风格与 PostgreSQL 保持一致。
八、总结
快照管理是 MVCC 的"地基",而 MVCC 又是 PostgreSQL 并发模型的"灵魂"。pgrust 用 Rust 重写这一部分时,既忠实还原了 xmin/xmax/xip 的可见性判断规则,又借助 Rust 的类型系统让快照的内存管理更安全。对于想学习数据库内核、或者关注 Rust 重写大型 C 项目的读者来说,pgrust 的 snapmgr 与可见性判断实现是一份非常值得精读的"活教材"。
如果你也想动手研究,克隆仓库后,从crates/backend/utils/time/snapmgr/src/lib.rs开始读起,再对照crates/backend/access/heap/heapam_visibility/src/lib.rs中的可见性判断,很快就能建立起 MVCC 的完整认知。
【免费下载链接】pgrustPostgres rewritten in Rust, now faster than Postgres and Clickhouse项目地址: https://gitcode.com/GitHub_Trending/pg/pgrust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考