Boltdb 源码阅读
背景
- 之前对db的了解更多的是黑盒,包括索引那些如何建立,更多的是八股文的记忆
- 虽说是go的代码,但读 boltDB 整体更像读c的代码,底层涉及到的更多是磁盘/内存 寻址
介绍
- boltdb和mysql一样采用了b+ tree的数据结构,适合读多写少的场景。
- b+ tree的平衡相关的代码在 spill(页分裂)和 rebanlance(页合并)方法内,并不难理解。
- 分支节点只存key,叶子节点存key和value
- 当叶子节点内容较少时,可以把key和value直接塞入父节点的item中,是一种io优化,方便读取
事务相关
- 读事务,读的是page在内存中的映射node。
- 写事务,重新把page拉取到内存(COW机制),只有在commit的时候才会落盘,并且更新meta。
磁盘页存储
- Page,就是header,用于标识当前page是什么类型
- pageElement,对item的meta信息记录
- item,具体的二进制落盘数据
磁盘内存映射
数据读取
// search recursively performs a binary search against a given page/node until it finds a given key.
// 检索key,有node就走node检索,有page就走page检索
func (c *Cursor) search(key []byte, pgid pgid) {
p, n := c.bucket.pageNode(pgid)
if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 {
panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags))
}
e := elemRef{page: p, node: n}
// 记录走过的节点(page,node,index)栈,方便方法之间调用(减少参数)
c.stack = append(c.stack, e)
// If we're on a leaf page/node then find the specific node.
if e.isLeaf() {
c.nsearch(key)
return
}
// 有内存node,走node检索,找到对应的节点后,递归进行search
if n != nil {
c.searchNode(key, n)
return
}
// 只有磁盘page,走磁盘page检索,找到对应的节点后,递归进行search
c.searchPage(key, p)
}
数据写入
// put inserts a key/value.
func (n *node) put(oldKey, newKey, value []byte, pgid pgid, flags uint32) {
if pgid >= n.bucket.tx.meta.pgid {
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", pgid, n.bucket.tx.meta.pgid))
} else if len(oldKey) <= 0 {
panic("put: zero-length old key")
} else if len(newKey) <= 0 {
panic("put: zero-length new key")
}
// Find insertion index.
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, oldKey) != -1 })
// 二分插入排序,保证整个nodes是有序的
// Add capacity and shift nodes if we don't have an exact match and need to insert.
exact := (len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].key, oldKey))
if !exact {
n.inodes = append(n.inodes, inode{})
copy(n.inodes[index+1:], n.inodes[index:])
}
inode := &n.inodes[index]
inode.flags = flags
inode.key = newKey
inode.value = value
inode.pgid = pgid
_assert(len(inode.key) > 0, "put: zero-length inode key")
}