diff --git a/docs/src/design/images/bplus-tree.jpg b/docs/src/design/images/bplus-tree.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2ad7fb97485f3c596e501677ab3a4e3e1938b001
Binary files /dev/null and b/docs/src/design/images/bplus-tree.jpg differ
diff --git a/docs/src/design/images/miniob-bplus-tree-deletion-migration.png b/docs/src/design/images/miniob-bplus-tree-deletion-migration.png
new file mode 100644
index 0000000000000000000000000000000000000000..a28e564b6838985acd28cb668cb814ee236fb535
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-deletion-migration.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-deletion-move.png b/docs/src/design/images/miniob-bplus-tree-deletion-move.png
new file mode 100644
index 0000000000000000000000000000000000000000..f35cbd0ea51def9920966fe625dd023eed36fecf
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-deletion-move.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-deletion-move2.png b/docs/src/design/images/miniob-bplus-tree-deletion-move2.png
new file mode 100644
index 0000000000000000000000000000000000000000..bc105810178aab6b6c8c66c3f73dd40d28385b4d
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-deletion-move2.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-deletion.png b/docs/src/design/images/miniob-bplus-tree-deletion.png
new file mode 100644
index 0000000000000000000000000000000000000000..1baad157992fc4feb4b10af5711f607da6c99b4e
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-deletion.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-index-file.png b/docs/src/design/images/miniob-bplus-tree-index-file.png
new file mode 100644
index 0000000000000000000000000000000000000000..25ec1dfa823a302066f24e9774d14898c04a6e46
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-index-file.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-internal-node.png b/docs/src/design/images/miniob-bplus-tree-internal-node.png
new file mode 100644
index 0000000000000000000000000000000000000000..670bb627c605bc386d482aa235b7921c7b406dea
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-internal-node.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-internal-struct.png b/docs/src/design/images/miniob-bplus-tree-internal-struct.png
new file mode 100644
index 0000000000000000000000000000000000000000..9b88e19aa47d28a77c051bf4aba9d4ca01db4dc0
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-internal-struct.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-internal-struct2.png b/docs/src/design/images/miniob-bplus-tree-internal-struct2.png
new file mode 100644
index 0000000000000000000000000000000000000000..1304315f40f77ce6d778153ba4fa226744265285
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-internal-struct2.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-leaf-node.png b/docs/src/design/images/miniob-bplus-tree-leaf-node.png
new file mode 100644
index 0000000000000000000000000000000000000000..b2407ec32f4f3d4c1e67e2ddf84562edaa44975c
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-leaf-node.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-leaf-page.png b/docs/src/design/images/miniob-bplus-tree-leaf-page.png
new file mode 100644
index 0000000000000000000000000000000000000000..20d915f624f6eaf13b6a508f53a64f9982c1ae7f
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-leaf-page.png differ
diff --git a/docs/src/design/images/miniob-bplus-tree-pages-in-file.png b/docs/src/design/images/miniob-bplus-tree-pages-in-file.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb16672e883867672155336f4b9582720d9b0481
Binary files /dev/null and b/docs/src/design/images/miniob-bplus-tree-pages-in-file.png differ
diff --git a/docs/src/design/images/miniob-buffer-pool-directory.png b/docs/src/design/images/miniob-buffer-pool-directory.png
new file mode 100644
index 0000000000000000000000000000000000000000..c08715ca70103ebf380702c5be824c88d6782af3
Binary files /dev/null and b/docs/src/design/images/miniob-buffer-pool-directory.png differ
diff --git a/docs/src/design/images/miniob-buffer-pool-implementation.png b/docs/src/design/images/miniob-buffer-pool-implementation.png
new file mode 100644
index 0000000000000000000000000000000000000000..0d985b377a0e203b29d1160fd14d1d3b4e6bb85c
Binary files /dev/null and b/docs/src/design/images/miniob-buffer-pool-implementation.png differ
diff --git a/docs/src/design/images/miniob-buffer-pool-page.png b/docs/src/design/images/miniob-buffer-pool-page.png
new file mode 100644
index 0000000000000000000000000000000000000000..b22f81fa2b43426dd5374ee1efc0414ffe4a5d32
Binary files /dev/null and b/docs/src/design/images/miniob-buffer-pool-page.png differ
diff --git a/docs/src/design/images/miniob-buffer-pool-record.png b/docs/src/design/images/miniob-buffer-pool-record.png
new file mode 100644
index 0000000000000000000000000000000000000000..6dfd3d3cc2956d0b6bf1c4ad14dc9882309db52a
Binary files /dev/null and b/docs/src/design/images/miniob-buffer-pool-record.png differ
diff --git a/docs/src/design/images/miniob-overview.png b/docs/src/design/images/miniob-overview.png
new file mode 100644
index 0000000000000000000000000000000000000000..4bdb5d346610c16dfc788652b5a7d9aa3c2d73f0
Binary files /dev/null and b/docs/src/design/images/miniob-overview.png differ
diff --git a/docs/src/design/miniob-bplus-tree-concurrency.md b/docs/src/design/miniob-bplus-tree-concurrency.md
new file mode 100644
index 0000000000000000000000000000000000000000..5bba20b8b697c3d005524fb1b948cf6580ed5fe3
--- /dev/null
+++ b/docs/src/design/miniob-bplus-tree-concurrency.md
@@ -0,0 +1,165 @@
+
+> [MiniOB](https://github.com/oceanbase/miniob) 是 [OceanBase](https://github.com/oceanbase/oceanbase) 联合华中科技大学推出的一款用于教学的小型数据库系统,希望能够帮助数据库爱好者系统性的学习数据库原理与实战。
+# B+ 树介绍
+
+
+B+ 树是传统数据库中常见的索引数据结构,比如MySQL、PostgreSQL都实现了B+树索引。B+ 树是一个平衡多叉树,层级少(通常只有3层)、数据块(内部节点/叶子节点)大小固定,是一个非常优秀的磁盘数据结构。关于B+ 树的原理和实现,网上有非常多的介绍,就不在此聒噪。这里将介绍如何实现支持并发操作的B+树以及MiniOB中的实现。
+
+
+# B+树的并发操作
+在多线程并发操作时,通常使用的手段是加锁,这里的实现方法也是这样。不过在学习并发B+树实现原理之前,需要对B+树的实现比较熟悉,有兴趣的同学可以网上搜索一下。
+
+## Crabing Protocol
+在操作B+树时加对应的读写锁是一种最简单粗暴但是有效的方法,只是这样实现效率不高。于是就有一些研究创建了更高效的并发协议,并且会在协议设计上防止死锁的发生。
+![B+树示例](images/bplus-tree.jpg)
+
+
+B+树是一个树状的结构,并且所有的数据都是在叶子节点上,每次操作,几乎都是从根节点开始向下遍历,直到找到对应的叶子节点。然后在叶子节点执行相关操作,如果对上层节点会产生影响,必须需要重新平衡,那就反方向回溯调整节点。
+Crabing协议是从根节点开始加锁,找到对应的子节点,就加上子节点的锁。一直循环到叶子节点。在拿到某个子节点锁时,如果当前节点是“安全的”,那就可以释放上级节点的锁。
+
+**什么是“安全的”**
+如果在操作某个节点时,可以确定这个节点上的动作,不会影响到它的父节点,那就说是“安全的”。
+B+树上节点的操作有三个:插入、删除和查询。
+
+- 插入:一次仅插入一个数据。如果插入一个数据后,这个节点不需要分裂,就是当前节点元素个数再增加一个,也不会达到一个节点允许容纳的最大个数,那就是安全的。不会分裂就不会影响到父节点。
+- 删除:一次仅删除一个数据。如果删除一个数据后,这个节点不需要与其它节点合并,就是当前节点元素个数删除一个后,也不会达到节点允许容纳的最小值,那就是安全的。不需要合并就不会影响到父节点。
+- 查询:读取数据对节点来说永远是安全的。
+
+B+树的操作除了上述的插入、删除和查询,还有一个扫描操作。比如遍历所有的数据,通常是从根节点,找到最左边的叶子节点,然后从向右依次访问各个叶子节点。此时与加锁的顺序,与之前描述的几种方式是不同的,那为了防止死锁,就需要对遍历做特殊处理。一种简单的方法是,在访问某个叶子节点时,尝试对叶子节点加锁,如果判断需要等待,那就退出本次遍历扫描操作,重新来一遍。当然这种方法很低效,有兴趣的同学可以参考[2],了解更高效的扫描加锁方案。
+
+问题:哪种场景下,扫描加锁可能会与更新操作的加锁引起死锁?
+问题:请参考[2],给出一个遍历时不需要重试的加锁方案。
+
+## MiniOB实现
+MiniOB的B+树并发实现方案与上个章节描述的方法是一致的。这里介绍一些实现细节。
+> 在这里假设同学们对B+树的实现已经有了一定的了解。
+
+### B+树与Buffer Pool
+B+树的数据是放在磁盘上的,但是直接读写磁盘是很慢的一个操作,因此这里增加一个内存缓冲层,叫做Buffer Pool。了解数据库实现的同学对这个名词不会陌生。在MiniOB中,Buffer Pool的实现是 `class DiskBufferPool`。对Buffer Pool实现不太了解也没关系,这里接单介绍一下。
+
+`DiskBufferPool` 将一个磁盘文件按照页来划分(假设一页是8K,但是不一定),每次从磁盘中读取文件或者将数据写入到文件,都是以页为单位的。在将文件某个页面加载到内存中时,需要申请一块内存。内存通常会比磁盘要小很多,就需要引入内存管理。在这里引入Frame(页帧)的概念(参考 `class Frame`),每个Frame关联一个页面。`FrameManager`负责分配、释放Frame,并且在没有足够Frame的情况下,淘汰掉一些Frame,然后将这些Frame关联到新的磁盘页面。
+
+那如何知道某个Frame关联的页面是否可以释放,然后可以与其它页面关联?
+如果这个Frame没有任何人使用,就可以重新关联到其它页面。这里使用的方法是引用计数,称为 `pin_count`。每次获取某个Frame时,`pin_count`就加1,操作完释放时,`pin_count`减1。如果`pin_count`是0,就可以将页面数据刷新到磁盘(如果需要的话),然后将Frame与磁盘文件的其它数据块关联起来。
+
+为了支持并发操作,Frame引入了读写锁。操作B+树时,就需要加对应的读写锁。
+
+B+ 树的数据保存在磁盘,其树节点,包括内部节点和叶子节点,都对应一个页面。当对某个节点操作时,需要申请相应的Frame,`pin_count`加1,然后加读锁/写锁。由于访问子节点时,父节点的锁可能可以释放,也可能不能释放,那么需要记录下某个某个操作在整个过程中,加了哪些锁,对哪些frame 做了pin操作,以便在合适的时机,能够释放掉所有相关的资源,防止资源泄露。这里引入`class LatchMemo` 记录当前访问过的页面,加过的锁。
+
+问题:为什么一定要先执行解锁,再执行unpin(frame引用计数减1)?
+
+### 处理流程
+B+树相关的操作一共有4个:插入、删除、查找和遍历/扫描。这里对每个操作的流程都做一个汇总说明,希望能帮助大家了解大致的流程。
+
+**插入操作**
+除了查询和扫描操作需要加读锁,其它操作都是写锁。
+
+```cpp
+- leaf_node = find_leaf // 查找叶子节点是所有操作的基本动作
+ memo.init // memo <=> LatchMemo,记录加过的锁、访问过的页面
+ lock root page
+ - node = crabing_protocal_fetch_page(root_page)
+ loop: while node is not leaf // 循环查找,直到找到叶子节点
+ child_page = get_child(node)
+ - node = crabing_protocal_fetch_page(child_page)
+ frame = get_page(child_page, memo)
+ lock_write(memo, frame)
+ node = get_node(frame)
+ // 如果当前节点是安全的,就释放掉所有父节点和祖先节点的锁、pin_count
+ release_parent(memo) if is_safe(node)
+
+- insert_entry_into_leaf(leaf_node)
+ - split if node.size == node.max_size
+ - loop: insert_entry_into_parent // 如果执行过分裂,那么父节点也会受到影响
+
+- memo.release_all // LatchMemo 帮我们做资源释放
+```
+
+**删除操作**
+与插入一样,需要对操作的节点加写锁。
+
+```cpp
+- leaf_node = find_leaf // 查找的逻辑与插入中的相同
+- leaf_node.remove_entry
+- node = leaf_node
+- loop: coalesce_or_redistribute(node) if node.size < node.min_size and node is not root
+ neighbor_node = get_neighbor(node)
+ // 两个节点间的数据重新分配一下
+ redistribute(node, neighbor_node) if node.size + neighbor_node.size > node.max_size
+ // 合并两个节点
+ coalesce(node, neighbor_node) if node.size + neighbor_node.size <= node.max_size
+
+ memo.release_all
+```
+
+**查找操作**
+查找是只读的,所以只加读锁
+
+```cpp
+- leaf_node = find_leaf // 与插入的查找叶子节点逻辑相同。不过对所有节点的操作都是安全的
+- return leaf_node.find(entry)
+- memo.release_all
+```
+
+**扫描/遍历操作**
+
+```cpp
+- leaf_node = find_left_node
+ loop: node != nullptr
+ scan node
+ node_right = node->right // 遍历直接从最左边的叶子节点,一直遍历到最右边
+ return LOCK_WAIT if node_right.try_read_lock // 不直接加锁,而是尝试加锁,一旦失败就返回
+ node = node_right
+ memo.release_last // 释放当前节点之前加到的锁
+```
+
+### 根节点处理
+前面描述的几个操作,没有特殊考虑根节点。根节点与其它节点相比有一些特殊的地方:
+- B+树有一个单独的数据记录根节点的页面ID,如果根节点发生变更,这个数据也要随着变更。这个数据不是被Frame的锁保护的;
+- 根节点具有一定的特殊性,它是否“安全”,就是根节点是否需要变更,与普通节点的判断有些不同。
+
+按照上面的描述,我们在更新(插入/删除)执行时,除了对节点加锁,还需要对记录根节点的数据加锁,并且使用独特的判断是否“安全的”方法。
+
+在MiniOB中,可以参考`LatchMemo`,是直接使用xlatch/slatch对Mutex来记录加过的锁,这里可以直接把根节点数据保护锁,告诉LatchMemo,让它来负责相关处理工作。
+判断根节点是否安全,可以参考`IndexNodeHandler::is_safe`中`is_root_node`相关的判断。
+
+### 如何测试
+想要保证并发实现没有问题是在太困难了,虽然有一些工具来证明自己的逻辑模型没有问题,但是这些工具使用起来也很困难。这里使用了一个比较简单的方法,基于google benchmark框架,编写了一个多线程请求客户端。如果多个客户端在一段时间内,一直能够比较平稳的发起请求与收到应答,就认为B+树的并发没有问题。测试代码在`bplus_tree_concurrency_test.cpp`文件中,这里包含了多线程插入、删除、查询、扫描以及混合场景测试。
+
+## 其它
+
+### 有条件的开启并发
+MiniOB是一个用来学习的小型数据库,为了简化上手难度,只有使用-DCONCURRENCY=ON时,并发才能生效,可以参考 mutex.h中`class Mutex`和`class SharedMutex`的实现。当CONCURRENCY=OFF时,所有的加锁和解锁函数相当于什么都没做。
+
+### 并发中的调试
+死锁是让人非常头疼的事情,我们给Frame增加了调试日志,并且配合pin_count的动作,每次加锁、解锁以及pin/unpin都会打印相关日志,并在出现非预期的情况下,直接ABORT,以尽早的发现问题。这个调试能力需要在编译时使用条件 `-DDEBUG=ON` 才会生效。
+以写锁为例:
+
+```cpp
+void Frame::write_latch(intptr_t xid)
+{
+ {
+ std::scoped_lock debug_lock(debug_lock_); // 如果非DEBUG模式编译,什么都不会做
+ ASSERT(pin_count_.load() > 0, // 加锁时,pin_count必须大于0,可以想想为什么?
+ "frame lock. write lock failed while pin count is invalid. "
+ "this=%p, pin=%d, pageNum=%d, fd=%d, xid=%lx, lbt=%s", // 这里会打印各种相关的数据,帮助调试
+ this, pin_count_.load(), page_.page_num, file_desc_, xid, lbt()); // lbt会打印出调用栈信息
+
+ ASSERT(write_locker_ != xid, "frame lock write twice." ...);
+ ASSERT(read_lockers_.find(xid) == read_lockers_.end(),
+ "frame lock write while holding the read lock." ...);
+ }
+
+ lock_.lock();
+ write_locker_ = xid;
+
+ LOG_DEBUG("frame write lock success." ...); // 加锁成功也打印一个日志。注意日志级别是DEBUG
+}
+```
+
+# 参考
+[1] [15445 indexconcurrency](https://15445.courses.cs.cmu.edu/fall2021/notes/08-indexconcurrency.pdf)
+
+[2] Concurrency of Operations on B-Trees
+
+[3] MySQL/MariaDB mini trans相关代码
\ No newline at end of file
diff --git a/docs/src/design/miniob-bplus-tree.md b/docs/src/design/miniob-bplus-tree.md
new file mode 100644
index 0000000000000000000000000000000000000000..7359ab5af8f69c3c2ff05466817953577b15ff17
--- /dev/null
+++ b/docs/src/design/miniob-bplus-tree.md
@@ -0,0 +1,66 @@
+# MiniOB B+Tree 实现
+
+## 简介
+
+在基本的逻辑上,MiniOB 的 B+Tree 和 B+Tree 是一致的,查询和插入都是从根逐层定位到叶结点,然后在叶结点内获取或者插入。如果插入过程发生叶结点满的情况,同样会进行分裂,并向上递归这一过程。
+
+
+
+如上图,每个结点组织成一个固定大小的 page,之前介绍过每个 page 首先有一个 page_num 表示 page 在文件中的序号,每个结点 page 都有一个common header 实现为 IndexNode 结构,其中包括 is_leaf(是否为叶结点)、key_num(结点中 key 的个数)、parent(结点父结点的 page num),当 parent=-1 时表示该结点没有父结点。
+
+除此之外,Leaf page 还有 prev_brother(左结点的 page num)和 next_brother(右结点的 page num),这两项用于帮助遍历。最后 page 所剩下的空间就顺序存放键值对,叶结点所存放的 key 是索引列的值加上 RID(该行数据在磁盘上的位置),Value 则为 RID,也就是说键值数据都是存放在叶结点上的,和 B+Tree 中叶结点的值是指向记录的指针不同。
+
+
+
+内部结点和叶结点有两点不同,一个是没有左右结点的 page num;另一个是所存放的值是 page num,也就是标识了子结点的 page 位置。如上图所示,键值对在内部结点是这样表示的,第一个键值对中的键是一个无效数据,真正用于比较的只有 k1 和 k2。
+
+
+
+所有的结点(即 page)都存储在外存的索引文件 IndexFile 中,其中文件的第一个 page 是索引文件头,存储了一些元数据,如 root page 的 page num,内部结点和叶子结点能够存储键值对的最大个数等。
+
+
+
+上图是一个简单的 MiniOB B+Tree 示例,其中叶结点能够访问到左右结点,并且每个结点能够访问到父结点。我们能够从 IndexFile 的第一个 page 得到 root page,而在知道一棵 B+Tree 的 root page 以后就足够访问到任意一个结点了。查询时我们会从 root page 开始逐层向下定位到目标叶结点,在每个 page 内遍历搜索查找键。
+
+## 插入
+
+在插入时,我们首先定位到叶结点,如下图中的 page2,然后在结点内定位一个插入位置,如果结点未满,那么将键值对插入指定位置并向后移动部分数据即可;如果结点已满,那么需要对其进行分裂。
+
+我们将先创建一个新的右兄弟结点,即 page5,然后在原结点内保留前一半的键值对,剩余的键值对则移动到新结点,并修改 page2 的后向 page num,page5 的前后向 page num 以及 page4 的前向 page num,再根据之前定位的插入位置判断是插入 page2 还是 page5 ,完成叶结点的插入。
+
+
+
+此外,由于我们新增了结点,我们需要在父结点也插入新的键值对,这一步将涉及到原结点,新结点以及新结点中的最小键,分为以下两种情况:
+
+1. 有父结点,那么直接将新结点中的最小键以及新结点的 page num 作为键值对插入父结点即可。
+
+
+2. 假设此时没有父结点,那么我们将创建一个新的根结点,除了把新结点键值对插入,还会将原结点的 page num 作为第一个键值对的值进行插入。
+
+
+
+如果父结点的键值对插入同样触发了分裂,我们将按上述的步骤递归执行。
+
+## 删除
+
+正常的删除操作我们就不再介绍,这里介绍一些涉及结点合并的特殊情况。
+
+首先在结点内删除键值对,然后判断其中的键值对数目是否小于一半,如果是则需要进行特殊处理。比如 page2 中删除一个键值对,导致其键值对数目小于一半,此时通过它的父结点找到该结点的左兄弟,如果是最左边的结点,则找到其右兄弟。
+
+
+
+- 如果两个结点的所有键值对能容纳在一个结点内,那么进行合并操作,将右结点的数据迁移到左结点,并删除父结点中指向右结点的键值对。
+
+
+
+- 如果两个结点的所有键值对不能容纳在一个结点内,那么进行重构操作。
+
+ - 当所删除键值对的结点不是第一个结点时,那么选择将左兄弟的最后一个键值对移动到当前结点,并修改父结点中指向当前结点的键。
+
+
+
+ - 当所删除键值对的结点是第一个结点时,那么选择将右兄弟的第一个键值对移动到当前结点,并修改父结点中指向右兄弟的键。
+
+
+
+在上述两种操作中,合并操作会导致父结点删除键值对,因此会向上递归地去判断是否需要再次的合并与重构。
diff --git a/docs/src/design/miniob-buffer-pool.md b/docs/src/design/miniob-buffer-pool.md
new file mode 100644
index 0000000000000000000000000000000000000000..4aed44f8126189d0be437b0ae8638bc93186f5f7
--- /dev/null
+++ b/docs/src/design/miniob-buffer-pool.md
@@ -0,0 +1,81 @@
+# MiniOB 存储实现
+
+本节将从存储层面介绍 MiniOB 的实现。
+
+## MiniOB 框架简介
+
+首先回顾一下 MiniOB 的框架,在 MiniOB 概述章节已经简单的介绍过,本节重点介绍执行器(Executor)访问的存储引擎。
+
+
+
+存储引擎控制整个数据、记录是如何在文件和磁盘中存储,以及如何跟内部 SQL 模块之间进行交互。存储引擎中有三个关键模块:
+
+- Record Manager:组织记录一行数据在文件中如何存放。
+
+- Buffer Pool:文件跟内存交互的关键组件。
+
+- B+Tree:索引结构。
+
+## MiniOB 文件管理
+
+首先介绍 MiniOB 中文件是怎么存放,文件需要管理一些基础对象,如数据结构、表、索引。数据库在 MiniOB 这里体现就是一个文件夹,如下图所示,最上面就是一个目录,MiniOB 启动后会默认创建一个 sys 数据库,所有的操作都默认在 sys 中。
+
+
+
+一个数据库下会有多张表。上图示例中只有三张表,接下来以 test1 表为例介绍一下表里都存放什么内容。
+
+- test1.table:元数据文件,这里面存放了一些元数据。如:表名、数据的索引、字段类型、类型长度等。
+
+- test1.data:数据文件,真正记录存放的文件。
+
+- test1-i_name.index:索引文件,索引文件有很多个,这里只展示一个示例。
+
+## MiniOB Buffer Pool 模块介绍
+
+Buffer Pool 在传统数据库里是非常重要的基础组件。
+
+首先来了解一下为什么要有一个 Buffer Pool ,数据库的数据是存放在磁盘里的,但不能直接从磁盘中读取数据,而是需要先把磁盘的数据读取到内存中,再在 CPU 做一些运算之后,展示给前端用户。写入也是一样的,一般都会先写入到内存,再把内存中的数据写入到磁盘。这种做法也是一个很常见的缓存机制。
+
+
+
+接着来看 Buffer Pool 在 MiniOB 中是如何组织的。如上图所示,左边是内存,把内存拆分成不同的帧(frame)。假如内存中有四个 frame,对应了右边的多个文件,每个文件按照每页来划分,每个页的大小都是固定的,每个页读取时是以页为单位跟内存中的一个 frame 相对应。
+
+Buffer Pool 在 MiniOB 里面组织的时候,一个 DiskBufferPool 对象对应一个物理文件。所有的 DiskBufferPool 都使用一个内存页帧管理组件 BPFrameManager,他是公用的。
+
+再来看下读取文件时,怎么跟内存去做交互的。如上图所示,frame1 关联了磁盘中一个文件的页面,frame2 关联了另一个页面,frame3 是空闲页面,没有关联任何磁盘文件,frame4 也关联了一个页面。
+
+比如现在要去读取 file3 的 Page3 页面,首先需要从 BPFrameManager 里面去找一个空闲的 frame,很明显,就是 frame3,然后再把 frame3 跟它关联起来,把 Page3 的数据读取到 frame3 里。现在内存中的所有 frame 都对应了物理页面。
+
+如果再去读取一个页面,如 Page5,这时候已经找不到内存了,通常有两种情况:
+
+- 内存还有空闲空间,可以再申请一个 frame,跟 Page5 关联起来。
+
+- 内存没有空闲空间,还要再去读 Page4,已经没有办法去申请新的内存了。此时就需要从现有的 frame 中淘汰一个页面,比如把 frame1 淘汰掉了,然后把 frame1 跟 Page4 关联起来,再把 Page4 的数据读取到 frame1 里面。淘汰机制也是有一些淘汰条件和算法的,可以先做简单的了解,暂时先不深入讨论细节。
+
+
+
+再来看一下,一个物理的文件上面都有哪些组织结构,如上图所示。
+
+- 文件上的第一页称为页头或文件头。文件头是一个特殊的页面,这个页面上会存放一个页号,这个页号肯定都是零号页,即 page num 是 0。
+
+- page count 表示当前的文件一共有多少个页面。
+
+- allocated pages 表示已经分配了多少个页面。如图所示标灰的是已经分配的三个页面。
+
+- Bitmap 表示每一个 bit 位当前对应的页面的分配状态,1 已分配页面,0 空闲页面。
+
+当前这一种组织结构是有一个缺陷的,整个文件能够支持的页面的个数受页面大小的限制,也就是说能够申请的页面的个数受页面大小的限制的。有兴趣的,可以思考一下怎么能实现一个无限大或支持更大页面的算法。
+
+接下来介绍一下普通页面(除 PageHeader 外),普通页面对 Buffer Pool 来说,第一个字段是用四字节的 int 来表示,就是 page num。接下来是数据,这个数据是由使用 Buffer Pool 的一些模块去控制。比如 Record Manage 或 B+Tree,他们会定义自己的结构,但第一个字段都是 page num,业务模块使用都是 page data 去做组织。
+
+## MiniOB 记录管理
+
+记录管理模块(Record Manager)主要负责组织记录在磁盘上的存放,以及处理记录的新增与删除。需要尽可能高效的利用磁盘空间,尽量减少空洞,支持高效的查找和新增操作。
+
+MiniOB 的 Record Manager 做了简化,有一些假设,记录通常都是比较短的,加上页表头,不会超出一个页面的大小。另外记录都是固定长度的,这个简化让学习 MiniOB 变得更简单一点。
+
+
+
+上面的图片展示了 MiniOB 的 Record Manager 是怎么实现的,以及 Record 在文件中是如何组织的。
+
+Record Manage 是在 Buffer Pool 的基础上实现的,比如 page0 是 Buffer Pool 里面使用的元数据,Record Manage 利用了其他的一些页面。每个页面有一个头信息 Page Header,一个 Bitmap,Bitmap 为 0 表示最近的记录是不是已经有有效数据;1 表示有有效数据。Page Header 中记录了当前页面一共有多少记录、最多可以容纳多少记录、每个记录的实际长度与对齐后的长度等信息。