【WiredTiger 内核】B-Tree 与 update chain:未提交更新只挂链

💡 原文中文,约4200字,阅读约需10分钟。
📝

内容提要

本文介绍WiredTiger存储引擎中B-Tree叶页的内存结构:新键通过WT_INSERT skiplist插入,已有键的修改挂入WT_UPDATE链表,未提交更新仅挂链不写入磁盘。读路径按时间戳在链上查找可见版本,旧版本在reconcile时移入History Store。文章还提及truncate操作及与日志回放的边界,为后续Reconciliation章节铺垫。

🔎

延伸解读

未提交更新为何只挂链

WiredTiger 将未提交更新仅挂在内存的 update chain 上,不写入磁盘镜像,这是其 MVCC 实现的关键。这样设计使得读操作无需等待磁盘写入即可看到最新版本,同时通过时间戳和事务快照控制可见性。但这也意味着,如果系统崩溃,未提交更新会丢失,因此需要日志回放来保证持久性。理解这一点有助于把握 WiredTiger 在性能与一致性之间的权衡。

读路径的三级查找顺序

对于某个键,读操作先查内存中的 update chain,再查磁盘页上的值,最后查 History Store。这种顺序保证了即使主表页只保留最新已提交值,旧快照仍可通过链上未 reconcile 的版本或 History Store 获得。因此,eviction 清掉用户页后,旧事务仍能读到一致快照,体现了 MVCC 的健壮性。

truncate 与日志回放的边界

range truncate 在实现上仍走更新和可见性逻辑,但文档对 logged tree 上与并发插入的交互保留不确定性。这意味着 truncate 操作可能与日志回放存在已知边界问题,需要谨慎处理。读者在涉及 truncate 的场景中,应关注官方文档的更新,避免依赖未明确的语义。

Q&A

WiredTiger中,新插入的键和已有键的更新在内存中分别如何存储?

新插入的键通过WT_INSERT结构以skiplist形式插入到叶页的键间隙中;已有键的更新则挂入WT_UPDATE链表,每个键对应一条链表,删除操作以特殊的tombstone标记。

WiredTiger中未提交的更新会写入磁盘吗?

不会。未提交的更新只挂在内存的update chain上,不会进入用户表的磁盘镜像。只有reconciliation(通常由eviction或checkpoint触发)时,才会将最新已提交值写入磁盘,旧版本移入History Store。

WiredTiger中读取一个键的可见值时,查找顺序是怎样的?

查找顺序为:先在内存的update chain上查找对当前事务可见的更新;如果没有,再看磁盘页上的值是否可见;若仍不可见,最后查询History Store。

WiredTiger中truncate操作是如何实现的?

truncate操作通过start/stop cursor按页遍历,如果整页可以标记删除则进行fast-truncate,否则逐键标记tombstone(slow-truncate)。它仍走更新/可见性逻辑,且与logging回放存在已知边界。

WiredTiger中旧版本数据在eviction后如何被读取?

eviction清掉用户页后,旧版本数据要么还挂在尚未reconcile的update chain上,要么已移入History Store。旧读者通过查找update chain、磁盘页值或History Store来获取可见版本。

WiredTiger中B-Tree的叶页上,WT_INSERT和WT_UPDATE分别用于什么场景?

WT_INSERT用于新键的插入(页上原本没有该键),以skiplist形式挂在键间隙;WT_UPDATE用于已有键的更新、修改或删除,以链表形式挂在每个键上。

🏷️

标签

➡️

继续阅读