什么是Bolt?
Bolt是一个纯净的基于go语言编写的key-val数据库,该项目受到LMDB项目的启发,目标是提供一个不需要完整服务器的简单、快速、可靠的数据库。
Bolt稳定,API固定,文件格式固定。全单元测试覆盖和随机黑盒测试用于确保数据库一致性和线程安全性。Bolt目前用于高负载生产环境,为1TB的数据库提供服务。Shopify和Heroku等许多公司每天都使用Bolt支持的服务。
唯一不好的一点作者自认为没时间更新了,以下为作者原文:
Bolt的最初目标是提供一个简单的纯Go键/值存储,并且不会使具有无关功能的代码膨胀。为此,该项目取得了成功。但是,这个有限的范围也意味着项目已经完成。
维护开源数据库需要大量的时间和精力。对代码的更改可能会产生意想不到的,有时甚至是灾难性的影响,因此即使是简单的更改也需要数小时和数小时的仔细测试和验证。
不幸的是,我不再有时间或精力继续这项工作。Bolt处于稳定状态,并且已经成功使用了多年。因此,我认为将其置于当前状态是最谨慎的行动方式。
如果您有兴趣使用更具特色的Bolt版本,我建议您查看名为bbolt的CoreOS分支
Bolt使用介绍
安装
go get github.com/boltdb/bolt
使用以及相关API
本文不详细介绍具体API用法,而是结合使用,将相关的API进行具体介绍。
Bolt的组织结构是:db(数据库) -- bucket(桶)-- key/val(键值对)- Open 打开数据库
Open函数可以获得一个db对象,db对象对应一个Close函数
db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close()
- Update 读写操作模式 dbptr 为之前Open函数返回的数据库操作指针,或者可以理解为我们之前使用数据库进行操作的连接。
CreateBucketIfNotExists 是创建一个bucket的函数,也可以直接使用CreateBucket,名字长的这个相对安全一点,如果存在就不创建了,否则将会报错!
Put函数就是设置key-val对的函数,设置的内容存放在对应的bucket当中。
func setData(key, val string) error { //以读写方式操作数据库 dbptr.Update(func(tx *bolt.Tx) error { //创建水桶 b, err := tx.CreateBucketIfNotExists([]byte(bucket)) if err != nil { fmt.Println("failed to create bucket", err) return err } //设置key-val对 return b.Put([]byte(key), []byte(val)) }) return nil}
- View 只读操作模式
此种模式不涉及到对数据的修改,只有读取数据部分,同样是需要找到相同的bucket,然后寻找对应的key值。
View就是以只读方式操作数据库。
Get函数可以获得bucket中的key值。
func getData(key string) string { data := []byte{} //以只读方式操作数据库 dbptr.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(bucket)) data = b.Get([]byte(key)) return nil }) return string(data)}
- 事务的支持
万万没想到的是,这么小的数据库还能支持事务。事务的特点就是一次执行的多个数据库操作,要么一起成功,要么一起失败!这个是关系型数据库的必备特点,当发生错误时一个rollback就可以恢复现场,避免出现垃圾数据。关于ACID的问题,我们本文不进行讨论。我们重点来说说如何使用API以实现事务。
Begin函数代表开启一个事务。
Rollback函数可以在出错时回滚事务。
Commit函数在事务执行没有问题之后,进行整体提交。
func transactionTest() { //开启一个事务 tx, err := dbptr.Begin(true) if err != nil { fmt.Println("failed to ") } //函数退出时回滚 defer tx.Rollback() //找到一个水桶 b := tx.Bucket([]byte(bucket)) //设置name对应的值是fuhongxue err = b.Put([]byte("name"), []byte("fuhongxue")) return //在这里return是有很大区别的,此时不会执行后面的commit,而是执行rollback if err != nil { fmt.Println("failed to put name", err) return } //执行到这里时提交事务 tx.Commit()}
完整测试代码如下:
package mainimport ( "fmt" "github.com/boltdb/bolt")var dbptr *bolt.DBconst bucket = "mybucket"func setData(key, val string) error { //以读写方式操作数据库 dbptr.Update(func(tx *bolt.Tx) error { //创建水桶 b, err := tx.CreateBucketIfNotExists([]byte(bucket)) if err != nil { fmt.Println("failed to create bucket", err) return err } //设置key-val对 return b.Put([]byte(key), []byte(val)) }) return nil}func getData(key string) string { data := []byte{} //以只读方式操作数据库 dbptr.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(bucket)) data = b.Get([]byte(key)) return nil }) return string(data)}func transactionTest() { //开启一个事务 tx, err := dbptr.Begin(true) if err != nil { fmt.Println("failed to ") } //函数退出时回滚 defer tx.Rollback() //找到一个水桶 b := tx.Bucket([]byte(bucket)) //设置name对应的值是fuhongxue err = b.Put([]byte("name"), []byte("fuhongxue")) return //在这里return是有很大区别的,此时不会执行后面的commit,而是执行rollback if err != nil { fmt.Println("failed to put name", err) return } //设置name对应的值是fuhongxue2 err = b.Put([]byte("name"), []byte("fuhongxue2")) if err != nil { fmt.Println("failed to put name", err) return } //执行到这里时提交事务 tx.Commit()}func main() { //打开数据库文件,这个文件用于存放数据库的数据 db, err := bolt.Open("db.db", 0600, nil) if err != nil { fmt.Println("failed to open db file", err) return } dbptr = db defer dbptr.Close() setData("name", "yekai") transactionTest() fmt.Println(getData("name"))}
大家猜一猜执行结果会如何呢?
yekaideMacBook-Pro:test yk$ go run db.go yekai
因为transactionTest中存在一个return,导致事务并没有被提交,所以val值就是yekai,仍然与setData后的结果相同。
把return注释之后再来尝试以下!
yekaideMacBook-Pro:test yk$ go run db.go fuhongxue2
代码编写到此位置,通过使用可以发现,Bolt使用确实非常便捷!为了更好的理解,我们总结一下Bolt的使用和结构,都在图里了!