本篇文章又回到了 MiniOB 部分,让我们一起来学习一下 MiniOB 存储实现的原理。
首先看一下今天的主要内容:
MiniOB 是 OB 社区推出的用于入门学习的轻量版数据库,整个数据库代码仅有两万行,很适合刚入门的同学进行了解。
首先我们还是来回顾一下 MiniOB 的框架图:
本文章主要介绍的内容就是 “Executor” 模块与之交互的 “Storage Engine(存储引擎)”,存储引擎由三个重要部件组成:Record Manager、Buffer Pool、B±Tree。
首先我们来看一下 MiniOB 里面的文件是怎么存放的。
从上图可以看出,文件需要管理很多基础对象,包括:数据库、表和索引;
$ pwd
/Users/chris/OceanBase/miniob/build/miniob/db/sys
-rw------- 1 chris staff 48K 10 5 09:56 test1-idx_test1_id.index
-rw------- 1 chris staff 32K 10 5 09:55 test1.data
-rw-r--r-- 1 chris staff 326B 10 5 09:56 test1.table
-rw------- 1 chris staff 16K 10 5 09:51 test2.data
-rw------- 1 chris staff 265B 10 5 09:51 test2.table
-rw------- 1 chris staff 16K 10 5 09:51 test3.data
-rw------- 1 chris staff 265B 10 5 09:51 test3.table
元数据文件的内容是可见的,如下,可以看到明确存储的元数据信息:$ cat test1.table
{
"fields" :
[
{
"len" : 4,
"name" : "__trx",
"offset" : 0,
"type" : "ints",
"visible" : false
},
{
"len" : 4,
"name" : "id",
"offset" : 4,
"type" : "ints",
"visible" : true
}
],
"indexes" :
[
{
"field_name" : "id",
"name" : "idx_test1_id"
}
],
"table_name" : "test1"
}%
数据文件和索引文件是不可见的。接下来我们看一下 Buffer Pool,Buffer Pool 是存储层面特别重要的一个组件。
上图可以看到,左边是内存,右边是磁盘,通常不能直接从磁盘读取数据,而是将磁盘数据读取到内存,然后 CPU 跟内存数据交互做计算;Buffer Pool 在这里面起的作用是从磁盘中加载数据到内存,并且负责将内存中的数据刷到磁盘。
我们以上图为例,看一下 Buffer Pool 的整个工作流程:
上图是针对一个文件的页面构成的解释,上面有写含义已经标注了,最底下的 page num 和 page data 重点说一下,page num 是一个 4 字节的int,page data 的数据是由使用 Buffer Pool 的组件决定的,比如 repmanager。
最后这部分介绍一下 MiniOB 的记录管理,记录管理主要负责记录在磁盘上怎么存放、它的格式和增删改查这几个动作怎么做。
首先我们看 Buffer Pool 的代码文件 disk_buffer_pool.h
BPFrameManager 是管理内存的对象,我们可以简单认为它是一个链表,链表存放了一系列的内存页帧,内存页帧可以通过文件(file1)和页号(page1)来获取:
Frame *get(int file_desc, PageNum page_num);
DiskBufferPool 对象就是我们提到的 Buffer Pool。
这里面包含一些方法,比如:
unsigned int pin_count_ = 0;
然后我们再看看 Record Manager 的代码文件 record_manager.h
下面这段代码就是我们说的页头,每一个页面上都会有这个:
struct PageHeader {
int32_t record_num; // 当前页面记录的个数
int32_t record_capacity; // 最大记录个数
int32_t record_real_size; // 每条记录的实际大小
int32_t record_size; // 每条记录占用实际空间大小(可能对齐)
int32_t first_record_offset; // 第一条记录的偏移量
};
通过下图,我们也可以看到整个头文件由4个类构成,包含页操作、文件操作、页迭代和文件扫描:
页操作类里面包含对这个页的增删改查,文件操作也包含增删改查,这里就不赘述了:
RC insert_record(const char *data, RID *rid);
RC update_record(const Record *rec);
template <class RecordUpdater>
RC update_record_in_place(const RID *rid, RecordUpdater updater)
{
Record record;
RC rc = get_record(rid, &record);
if (rc != RC::SUCCESS) {
return rc;
}
rc = updater(record);
frame_->mark_dirty();
return rc;
}
RC delete_record(const RID *rid);
RC get_record(const RID *rid, Record *rec);
文件扫描和迭代也都比较好理解,打开文件扫描代码如下:
class RecordFileScanner {
public:
RecordFileScanner() = default;
/**
* 打开一个文件扫描。
* 如果条件不为空,则要对每条记录进行条件比较,只有满足所有条件的记录才被返回
*/
RC open_scan(DiskBufferPool &buffer_pool, ConditionFilter *condition_filter);
/**
* 关闭一个文件扫描,释放相应的资源
*/
RC close_scan();
bool has_next();
RC next(Record &record);
private:
RC fetch_next_record();
RC fetch_next_record_in_page();
private:
DiskBufferPool *disk_buffer_pool_ = nullptr;
BufferPoolIterator bp_iterator_;
ConditionFilter *condition_filter_ = nullptr;
RecordPageHandler record_page_handler_;
RecordPageIterator record_page_iterator_;
Record next_record_;
};
今天的内容大概就这些~
最后的最后,如果大家感兴趣,可以多关注和参与 OB 的活动:https://ask.oceanbase.com/t/topic/35601006。