今天介绍的库bolt是一个纯粹用go编写的key/value数据库,这个库的目的是为了提供一个简单,快速可靠的数据库同时无需单独安装一个例如Postgres或MysqL之类的负责的数据库服务。作者在介绍里面还提及了如何通过阅读代码来了解一个数据库的基本原理,感谢作者的无私奉献。
打开数据库package mainimport ( "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) return } defer db.Close() go worker() time.Sleep(10 * time.Second)}
如上面代码所示,main函数中打开kes.go后另起一个routine打开同样的数据库文件,就会阻塞直到超时:
$ go run bolt.goopen=timeoutTransactions
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"
VIEw常常和Cursor一起使用。
Batch read-write transactions如果多个routine执行了写入 *** 作,可以使用Batch
// Iterate over multiple updates in separate goroutines. n := 2 ch := make(chan error) for i := 0; i < n; i++ { go func(i @H_419_282@int) { ch <- db.Batch(func(tx *bolt.Tx) error { return tx.Bucket([]@H_419_282@byte("Widgets")).Put(u64tob(@H_419_282@uint64(i)),[]@H_419_282@byte{}) }) }(i) } // Check all responses to make sure there's no error. for i := 0; i < n; i++ { if err := <-ch; err != nil { t.Fatal(err) } }
但是程序报错
goroutine 5 [running]:panic(0xea9e0,0xc42000a0b0) /usr/local/Cellar/go/1.7.5/libexec/src/runtime/panic.go:500 +0x1a1main.main.func2.1(0xc4200aa1c0,0x117a48,0xc4200aa1c0) /Users/kes/documents/program-learn/go/src/bolt.go:57 +0x1c4github.com/boltdb/bolt.(*DB).Update(0xc420092000,0xc4200a0000,0x0,0x0) /Users/kes/goproject/src/github.com/boltdb/bolt/db.go:598 +0xb5github.com/boltdb/bolt.(*DB).Batch(0xc420092000,0x0) /Users/kes/goproject/src/github.com/boltdb/bolt/db.go:680 +0x306main.main.func2(0xc42001a180,0xc420092000,0x0) /Users/kes/documents/program-learn/go/src/bolt.go:58 +0x5ccreated by main.main /Users/kes/documents/program-learn/go/src/bolt.go:59 +0x212
在于
func u64tob(v uint64) []byte { b := make([]byte,8) binary.BigEndian.PutUint64(b,v) return b }
初始化 b:=make([]byte,8) 将8写为了0.
手动控制transactionDB.VIEw()和DB.UPdate()函数包括了DB.Begin(),这些函数会启动transaction,执行函数,然后在返回error的时候安全的关闭transaction.
DB.VIEw()和DB.UPdate()是Bolt推荐的使用方法。
有的时候你想手动启动和结束transactionn,就可以调用DB.Begin()函数来启动一个transaction然后调用Commmit或Rollback()来结束transaction.
可以参考源码中的测试用例:
func ExampleDB_Begin_Readonly() { // Open the database. db,err := bolt.Open(tempfile(), 0666,nil) if err != nil { log.Fatal(err) } defer os.Remove(db.Path()) // Create a bucket using a read-write transaction. if err := db.Update(func(tx *bolt.Tx) error { _,err := tx.CreateBucket([]@H_419_282@byte("Widgets")) return err }); err != nil { log.Fatal(err) } // Create several keys in a transaction. tx,err := db.Begin(true) if err != nil { log.Fatal(err) } b := tx.Bucket([]@H_419_282@byte("Widgets")) if err := b.Put([]@H_419_282@byte("john"),[]@H_419_282@byte("blue")); err != nil { log.Fatal(err) } if err := b.Put([]@H_419_282@byte("abby"),[]@H_419_282@byte("red")); err != nil { log.Fatal(err) } if err := b.Put([]@H_419_282@byte("zephyr"),[]@H_419_282@byte("purple")); err != nil { log.Fatal(err) } if err := tx.Commit(); err != nil { log.Fatal(err) } // Iterate over the values in sorted key order. tx,err = db.Begin(false) if err != nil { log.Fatal(err) } c := tx.Bucket([]@H_419_282@byte("Widgets")).Cursor() for k,v := c.First(); k != nil; k,v = c.Next() { fmt.Printf("%s likes %s\n",k,v) } if err := tx.Rollback(); err != nil { log.Fatal(err) } if err := db.Close(); err != nil { log.Fatal(err) } // Output: // abby likes red // john likes blue // zephyr likes purple}使用 buckets
Buckets是bolt数据库中存放key/value对的地方。一个bucket 中的所有key必须是唯一的,可以通过DB.CreateBucket()或CreateBucketIfNotExists来创建。
if err := db.Update(func(tx *bolt.Tx) error { if _,err) }
CreateBucketIfNotExists用来创建一个不存在的bucket,如果已经存在就不会创建。
删除一个buckets用函数Tx.DeleteBucket()
删除一个bucket,调用Tx.DeleteBucket()即可。
把一个key/value保存到bucket中,使用Bucket.Put():
if err := db.Update(func(tx *bolt.Tx) error { if _,err := tx.CreateBucketIfNotExists([]@H_419_282@byte("kes")); err != nil { logger.Log("create Failed",err) return err } b := tx.Bucket([]@H_419_282@byte("kes")) err = b.Put([]@H_419_282@byte("answer"),[]@H_419_282@byte("42")) return err // return nil }); err != nil { logger.Log("update",err) }
上面代码保存”answer”->”42”保存到bucket”kes”中,为了获取这个值,可以使用 Bucket.Get() 函数。
if err := db.VIEw(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("kes")) v := b.Get([]byte("answer")) fmt.Printf("the anser is :%s\n",v) return nil }); err != nil { logger.Log("vIEw",err) } if err := db.VIEw(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("kes")) v := b.Get([]byte("answernotexist")) fmt.Printf("the anser is :%s\n",err) }
看到输出
the anser is :42the anser is :
Get()函数不会返回错误,如果key存在,则返回byte slice值,如果不存在就会返回nil。
bucket自动递增整数key不存在和key对应的值是0长度的值是不一样的。
通过使用NextSequence(),你可以让Bolt生成一个key/value的唯一标记。
for i := 0; i < 3; i++ { if err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("kes")) ID,_ := b.NextSequence() err := b.Put(u64tob(ID),[]byte("sds")) return err }); err != nil { logger.Log("vIEw",err) } }迭代便利keys
Bolt在bucket中按照byte-sorted order存储key,遍历这些key非常快。通过使用Cursor遍历key。
db.VIEw(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("kes")) c := b.Cursor() for k,v := c.First(); k != nil; k,v = c.Next() { fmt.Printf("key = %s,value = %s\n",v) } return nil })
bolt允许Cursor移动到特定的一点,当迭代到最后一个key的时候,再次调用Next会返回nil.
First() Move to the first key.Last() Move to the last key.Seek() Move to a specific key.Next() Move to the next key.Prev() Move to the prevIoUs key.
在迭代的过程中,如果key是non-nil但是value是nil,这就意味着key指向一个bucker而不是一个value,这时候调用Buckert.Bucket()来访问sub-bucker.
prefix scans搜索含有特定前缀的key,结合Seek和bytes.HasPrefix来实现。
db.VIEw(func(tx *bolt.Tx) error { // Assume bucket exists and has keys c := tx.Bucket([]byte("MyBucket")).Cursor() prefix := []byte("1234") for k,v := c.Seek(prefix); k != nil && bytes.HasPrefix(k,prefix); k,v = c.Next() { fmt.Printf("key=%s,value=%s\n",v) } return nil})范围搜索
db.VIEw(func(tx *bolt.Tx) error { // Assume our events bucket exists and has RFC3339 encoded time keys. c := tx.Bucket([]byte("Events")).Cursor() // Our time range spans the 90's decade. min := []byte("1990-01-01T00:00:00Z") max := []byte("2000-01-01T00:00:00Z") // Iterate over the 90's. for k,v := c.Seek(min); k != nil && bytes.Compare(k,max) <= 0; k,v = c.Next() { fmt.Printf("%s: %s\n",v) } return nil})
在前面讲过,bolt的key是依据byte-order排序的,所以seek的查询比较特别,参见下面示例代码:
if err := db.Update(func(tx *bolt.Tx) error { if _,err := tx.CreateBucketIfNotExists([]@H_419_282@byte("testseek")); err != nil { logger.Log("create Failed",err) return err } b := tx.Bucket([]@H_419_282@byte("testseek")) // fmt.Println("bucket") if err := b.Put([]@H_419_282@byte("cata"),[]@H_419_282@byte("0001")); err != nil { logger.Log(err) return err } if err := b.Put([]@H_419_282@byte("catc"),[]@H_419_282@byte("0002")); err != nil { logger.Log(err) return err } if err := b.Put([]@H_419_282@byte("catd"),[]@H_419_282@byte("0003")); err != nil { logger.Log(err) return err } return nil }); err != nil { logger.Log("update",err) } // fmt.Printf("update\n") // db.VIEw(func(tx *bolt.Tx) error { // b := tx.Bucket([]byte("testseek")) // b.ForEach(func(k,v []byte) error { // fmt.Printf("for each key = %s,value = %x\n",v) // return nil // }) // return nil // }) // fmt.Printf("foreach\n") //seek 借鉴seek_test的代码,在前面讲过,bolt的key是依据byte-order排序的,所以seek的查询比较特别 if err := db.VIEw(func(tx *bolt.Tx) error { c := tx.Bucket([]@H_419_282@byte("testseek")).Cursor() //精确匹配,就定位到"cata" k,v := c.Seek([]@H_419_282@byte("cata")) fmt.Printf("%s %s\n",v) //非精确匹配,返回byte-order在"catb"之后的第一个key,就是"catc" k,v = c.Seek([]@H_419_282@byte("catb")) fmt.Printf("%s %s\n",v) //非精确匹配,"cat"小于当前bucket中的任何一个key,则返回第一个key k,v = c.Seek([]@H_419_282@byte("cat")) fmt.Printf("%s %s\n",v) //非精确匹配,""小于当前bucket中的任何一个key,则返回第一个key k,v = c.Seek([]@H_419_282@byte("")) fmt.Printf("seek empty -> %s %s\n",v) //非精确匹配,"catz"小于当前bucket中的任何一个key,则返回nil k,v = c.Seek([]@H_419_282@byte("catz")) fmt.Printf("seek catz -> %s %s\n",v) return nil }); err != nil { logger.Log(err) }ForEach
可以通过ForEach()来便利bucket的所有key。
db.VIEw(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket([]@H_419_282@byte("MyBucket")) b.ForEach(func(k,v []@H_419_282@byte) error { fmt.Printf("key=%s,value=%s\n",v) return nil }) return nil})
foreach返回的key/value只再transaction中有效,那么如果要在transaction之外使用,要调用copy来把数据拷贝到别的slice中。
嵌套 buckets可以在bucket里面存储多个bucket
func createuser(accountID @H_419_282@int,u *User) error { // Start the transaction. tx,err := db.Begin(true) if err != nil { return err } defer tx.Rollback() // RetrIEve the root bucket for the account. // Assume this has already been created when the account was set up. root := tx.Bucket([]@H_419_282@byte(strconv.FormatUint(accountID, 10))) // Setup the users bucket. bkt,err := root.CreateBucketIfNotExists([]@H_419_282@byte("USERS")) if err != nil { return err } // Generate an ID for the new user. userID,err := bkt.NextSequence() if err != nil { return err } u.ID = userID // Marshal and save the encoded user. if buf,err := Json.Marshal(u); err != nil { return err } else if err := bkt.Put([]@H_419_282@byte(strconv.FormatUint(u.ID, 10)),buf); err != nil { return err } // Commit the transaction. if err := tx.Commit(); err != nil { return err } return nil}
上面是官网给的示例代码,比较负责,自己就写了一个简单的方便大家理解
if err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]@H_419_282@byte("kes")) bkt,err := b.CreateBucketIfNotExists([]@H_419_282@byte("user")) if err != nil { return err } bkt.Put([]@H_419_282@byte("nest key"),[]@H_419_282@byte("nest value")) return err }); err != nil { logger.Log("vIEw",err) } db.VIEw(func(tx *bolt.Tx) error { b := tx.Bucket([]@H_419_282@byte("kes")) b.ForEach(func(k,v []@H_419_282@byte) error { fmt.Printf("for each key = %s,v) return nil }) return nil })
在上面代码中,原始的bucket “kes”中嵌套了bucket “user”,然后遍历key/value得到
for each key = answer,value = 42for each key = newkye,value = sdsfor each key = user,value =
前两个是之前添加的真正的key/value,最后一个嵌套的bucket “user”的名称也作为key被遍历出来。
总结以上是内存溢出为你收集整理的golang bolt库 *** 作手册全部内容,希望文章能够帮你解决golang bolt库 *** 作手册所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)