上一章学习 DB 文件是如何通过字节组织成具体的文件、如何进行读写,本章将继续学习
1.BoltDB 如何实现事务?
2.BlotDB 如何实现 MVCC?
首先 BoltDB 是通过 B+ 树来组织 page 的。其中 db 文件,pageid 为 0,1 页固定用于存储 meta 数据。而 BlotDB 也是通过 meta 来实现事务,在 BlotDB 中,只有 meta 数据写入成功数据才能被其他新事务可见(具体代码阅读 tx.go 和 db.go)。
事务
先来看看一个事务写事务的开启:
func (db *DB) Begin(writable bool) (*Tx, error) {
if writable {
return db.beginRWTx()
}
return db.beginTx()
}
func (db *DB) beginRWTx() (*Tx, error) {
...
t := &Tx{writable: true} //初始化一个写写事务
t.init(db)
db.rwtx = t
db.freePages() //释放之前写事务产生是 pending 中的 ids,这样新事务可以直接利用这些空闲页
return t, nil
}
func (tx *Tx) init(db *DB) {
tx.db = db
tx.pages = nil
//拷贝元数据
tx.meta = &meta{}
db.meta().copy(tx.meta)
tx.root = newBucket(tx) //新的bucket(一个 bucket 管理一个事务缓存的 page 数据,事务之间数据隔离)
tx.root.bucket = &bucket{}
*tx.root.bucket = tx.meta.root //这个包含两个值一个是 (root pgid) 查找数据时从哪个page开始查找,(sequence uint64) 事务ID,事务 ID 是递增的,每次开启写事务+1
if tx.writable {
tx.pages = make(map[pgid]*page) //这个保存事务过程涉及脏页(数据被修改过的),后续将这些数据刷入磁盘。
tx.meta.txid += txid(1) //事务 ID+1
}
}
上面代码是一个事务初始化的过程。其主要三点,一是释放空闲页,二是拷贝meta,三是初始化一个新的 Bucket,用于管理整个事务过程涉及的数据。
BlotDB 一个简单写数据的例子:
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("test"))
if b != nil {
err := b.Put([]byte("hello"), []byte("world"))
if err != nil {
}
}
return nil
})
db.Update 函数用于初始化事务(上述的流程),执行 func、提交或者滚回事务。func 中,Put 流程大概可以总结为:
1.根据指定 key,从 meta.root 记录的根页开始,生成B+ 快照,查找(B+ 树节点元素是已经根据key 进行来排序,所以可以通过二分法查找以提高查询效率)指定 key 不大于 B+ 树节点 key 的位置。查看过程具体可以阅读 cursor.go 中的 seek。比如已有节点是下图