今天介绍的库bolt是一个纯粹用go编写的key/value数据库,这个库的目的是为了提供一个简单,快速可靠的数据库同时无需单独安装一个例如Postgres或MySQL之类的负责的数据库服务。作者在介绍里面还提及了如何通过阅读代码来了解一个数据库的基本原理,感谢作者的无私奉献。
打开数据库
package main
import (
"os"
"github.com/boltdb/bolt"
"github.com/go-kit/kit/log"
)
func main() {
logger := log.NewLogfmtLogger(os.Stdout)
db, err := bolt.Open("mydb.db", 0600, nil)
if err != nil {
logger.Log("open", err)
}
defer db.Close()
}
Bolt在打开数据库文件的时候会获取一个文件锁,所以多个进程不能同时打开一个数据库文件。
打开一个已经Open的Bolt数据库会导致当前进程挂起直到其他进程关闭该Bolt数据库。
为了避免无限等待你可以在打开数据库文件的时候制定一个超时时间。
func worker() {
logger := log.NewLogfmtLogger(os.Stdout)
db, err := bolt.Open("kes.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
logger.Log("open", err)
return
}
db.Close()
}
func main() {
logger := log.NewLogfmtLogger(os.Stdout)
db, err := bolt.Open("kes.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
logger.Log("open", err)
return
}
defer db.Close()
go worker()
time.Sleep(10 * time.Second)
}
如上面代码所示,main函数中打开kes.go后另起一个routine打开同样的数据库文件,就会阻塞直到超时:
$ go run bolt.go
open=timeout
Transactions
Bolt数据库同时只支持一个read-write transaction或者多个read-only transactions。
每个独立的transaction以及在这个transaction中创建的所有对象(buckerts,keys等)都不是thread safe的。如果要在多个routine中处理数据,那么必须在每个routine中单独使用一个transaction或者显式的使用lock以确保在每个时刻只有一个routine访问这个transaction.
read-only的transaction和read-write的transaction不应该相互之间有依赖,一般来说在同一个goroutine中不要同时打开这两种transaction,因为read-write transaction需要周期性的re-map数据文件,但是由于read-only transaction打开导致read-write transaction的re-map操作无法进行造成死锁。
Read-write transactions
通过DB.UPdate()打开一个read-write transaction.
if err := db.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucketIfNotExists([]byte("kes")); err != nil {
logger.Log("create failed", err)
return err
}
return nil
}); err != nil {
logger.Log("update", err)
}
在closure闭包内部,获取一个数据库的连续view。在closure最后返回nil完成commit的操作,可以在任何地方通过返回error完成rolleback的操作。
在read-write transaction中允许所有的数据库操作
func (db *DB) Update(fn func(*Tx) error) error {
t, err := db.Begin(true)
if err != nil {
return err
}
// Make sure the transaction rolls back in the event of a panic.
defer func() {
if t.db != nil {
t.rollback()
}
}()
// Mark as a managed tx so that the inner function cannot manually commit.
t.managed = true
// If an error is returned from the function then rollback and return error.
err = fn(t)
t.managed = false
if err != nil {
_ = t.Rollback()
return err
}
return t.Commit()
}
Read-Only transactions
通过 DB.View()函数打开一个read-only transaction。
if err := db.View(func(tx *bolt.Tx) error {
// if _, err := tx.CreateBucket([]byte("kes")); err != nil {
// logger.Log("create failed", err)
// return err
// }
return nil
}); err != nil {
logger.Log("view", err)
}
在read-only transaction中不允许更改操作。只能获取buckets,查询value,复制数据库。
上面的代码中,如果把注释掉的代码加上,就会报错
view="tx not writable"