第一节 boltdb的物理页page结构

在boltdb中,一个db对应一个真实的磁盘文件。而在具体的文件中,boltdb又是按照以page为单位来读取和写入数据的,也就是说所有的数据在磁盘上都是按照页(page)来存储的,而此处的页大小是保持和操作系统对应的内存页大小一致,也就是4k。

每页由两部分数据组成:页头数据+真实数据,页头信息占16个字节,下面的页的结构定义

  1. type pgid uint64
  2. type page struct {
  3. // 页id 8字节
  4. id pgid
  5. // flags:页类型,可以是分支,叶子节点,元信息,空闲列表 2字节,该值的取值详细参见下面描述
  6. flags uint16
  7. // 个数 2字节,统计叶子节点、非叶子节点、空闲列表页的个数
  8. count uint16
  9. // 4字节,数据是否有溢出,主要在空闲列表上有用
  10. overflow uint32
  11. // 真实的数据
  12. ptr uintptr
  13. }

其中,ptr是一个无类型指针,它就是表示每页中真实的存储的数据地址。而其余的几个字段(id、flags、count、overflow)为我们前面提到的页头信息。

下图展现的是boltdb中page的数据存储方式。

../imgs/boltdb物理页结构.png

在boltdb中,它把页划分为四类:

page页类型类型定义类型值用途
分支节点页branchPageFlag0x01存储索引信息(页号、元素key值)
叶子节点页leafPageFlag0x02存储数据信息(页号、插入的key值、插入的value值)
元数据页metaPageFlag0x04存储数据库的元信息,例如空闲列表页id、放置桶的根页等
空闲列表页freelistPageFlag0x10存储哪些页是空闲页,可以用来后续分配空间时,优先考虑分配

boltdb通过定义的常量来描述

  1. // 页头的大小:16字节
  2. const pageHeaderSize = int(unsafe.Offsetof(((*page)(nil)).ptr))
  3. const minKeysPerPage = 2
  4. //分支节点页中每个元素所占的大小
  5. const branchPageElementSize = int(unsafe.Sizeof(branchPageElement{}))
  6. //叶子节点页中每个元素所占的大小
  7. const leafPageElementSize = int(unsafe.Sizeof(leafPageElement{}))
  8. const (
  9. branchPageFlag = 0x01 //分支节点页类型
  10. leafPageFlag = 0x02 //叶子节点页类型
  11. metaPageFlag = 0x04 //元数据页类型
  12. freelistPageFlag = 0x10 //空闲列表页类型
  13. )

同时每页都有一个方法来判断该页的类型,我们可以清楚的看到每页时通过其flags字段来标识页的类型。

  1. // typ returns a human readable page type string used for debugging.
  2. func (p *page) typ() string {
  3. if (p.flags & branchPageFlag) != 0 {
  4. return "branch"
  5. } else if (p.flags & leafPageFlag) != 0 {
  6. return "leaf"
  7. } else if (p.flags & metaPageFlag) != 0 {
  8. return "meta"
  9. } else if (p.flags & freelistPageFlag) != 0 {
  10. return "freelist"
  11. }
  12. return fmt.Sprintf("unknown<%02x>", p.flags)
  13. }

下面我们一一对其数据结构进行分析。