Bustub中表的设计
Bustub中的Table
首先,我们先来看一下Bustub数据库中表的设计的结构图:
首先,Bustub 有一个 Catalog。Catalog 提供了一系列 API,例如 CreateTable()
、GetTable()
等等。Catalog 维护了几张 hashmap,保存了 table id 和 table name 到 table info 的映射关系。table id 由 Catalog 在新建 table 时自动分配,table name 则由用户指定。
这里的 table info 包含了一张 table 的 metadata,有 schema、name、id 和指向 table heap 的指针。系统的其他部分想要访问一张 table 时,先使用 name 或 id 从 Catalog 得到 table info,再访问 table info 中的 table heap。
table heap 是管理 table 数据的结构,包含 InsertTuple()
、MarkDelete()
一系列 table 相关操作。table heap 本身并不直接存储 tuple 数据,tuple 数据都存放在 table page 中。table heap 可能由多个 table page 组成,仅保存其第一个 table page 的 page id。需要访问某个 table page 时,通过 page id 经由 buffer pool 访问。
table page 是实际存储 table 数据的结构,父类是 page。相较于 page,table page 多了一些新的方法。table page 在 data 的开头存放了 next page id、prev page id 等信息,将多个 table page 连成一个双向链表,便于整张 table 的遍历操作。当需要新增 tuple 时,table heap 会找到当前属于自己的最后一张 table page,尝试插入,若最后一张 table page 已满,则新建一张 table page 插入 tuple。table page 低地址存放 header,tuple 从高地址也就是 table page 尾部开始插入。
tuple 对应数据表中的一行数据。每个 tuple 都由 RID 唯一标识。RID 由 page id + slot num 构成。tuple 由 value 组成,value 的个数和类型由 table info 中的 schema 指定。
value 则是某个字段具体的值,value 本身还保存了类型信息。
Tuple
上面给出了一条Tuple
的结构,如果我们想解析一条Tuple
,例如知道Tuple
的每一个Value
,包括类型,我们就需要另外一个叫做Schema
的类了。所以说Tuple
需要和Schema
配合使用。
Schema
定义好了一个表的表模式,即一个table由哪些列组成的,列的名称,占用的大小等。Tuple
中有一些列是由Varchar组成的字符串,由于这些字符串长度的不确定,我们又该如何在Tuple
中进行存储呢?
我们可以先计算Schema
中固定的长度,也就是比如一些int、bool等类型的column,这些存储的位置都可以固定下来,并且可以用这些加起来得到schema
的长度,而对于变长的字符串,我们留一个offset
,再在整个shema
固定部分的长度后面加进去。这也就是Column
中函数IsInlined()
的作用,就是判断data_
的 column->GetOffset()
处 存储的是真实的Value,还是二级的offset。这样就解决了Varchar的可边长问题:
我们再来看一下Tuple
的结构,data_
字符数组就是这样组织的,主要还是看代码
Tuple
除了data_
来存储各个column外,还有
1 |
|
这三个成员。
TablePage
bustub中存储table表的页叫做TablePage
,它的结构如下:
1 |
|
下面就是TablePage
的结构,前面有24个字节的头部,分别存储了Page的基本信息以及FreeSpacePointer
和TupleCount
,通过这两个就可以实现对TablePage
的解析。每一个offset
就是Page中Tuple
的data_
在页的起始位置的偏移,至于RID
就是offset size
数组的下标。
在Bustub中是如何删除某一条Tuple
的呢,首先先对Tuple
进行删除的标记,也就是调用MarkDelete
函数。
事务首先调用MarkDelete,然后如果提交成功,使用ApplyDelete函数从物理上删除。如果事务异常终止了则调用RollbackDelete函数再把每个tuple的size的第32位1给抹去。
1 |
|
这个函数做一些判断后调用SetTupleSize
。这个标志就相当于把tuple_size
的第32位置1,根据这一位判断是否是需要被Delete
的。同样UnsetDeletedFlag
函数就是获取设为Delete
标志位前tuple
的长度。
1 |
|
ApplyDelete
接下来我们可以直接看ApplyDelete
函数,这个函数是把tuple
的内容从TablePage
中进行物理删除。
这个函数的行为是把rid
这个tuple
给删除了,首先利用UnsetDeletedFlag
函数得到原来的tuple_size
,然后把使用memmove
函数直接把需要删除的tuple
给覆盖掉。然后再把移动了的tuple
(offset
小于被删除tuple
的offset
)的偏移加上删除tuple
的tuple_size
。还需要把相应位置的offset
和size
都置为0。
1 |
|
InsertTuple
InsertTuple
函数负责把tuple
中的data_
部分插入到TablePage
中,再把插入的位置记录在指针rid
中。
首先判断是否插入得下,如果没有足够的空间插入就返回false
。
然后开始遍历所有的tuple
(从0到TupleCount()
,这里的i
就是RID
中的slot_num
),。如果某个tuple
的size
为0,说明这个位置是空闲的(之前ApplyDelete
的时候offset
和size
数组在page中的的位置并没有改变,删除了只是把它们都置为0。
然后我们把FreeSpacePointer
减去tuple.size_
,并把tuple.data_
搬运到这个位置上,最后设置rid
,rid->Set(GetTablePageId(), i)
。
1 |
|