BoltDB 入门实践

2019-10-04 16:03| 发布者: |

Abser

TechCats 成员/朋克程序员:看看,这就叫专业

go get go.etcd.io/bbolt/...
复制代码

会 get 两项

使用 kv 数据库都很简单,只需要一个文件路径即可搭建完成环境。

package main
import  {
 // Open the my.db data file in your current directory.
 // It will be created if it doesn t exist.
 db, err := bolt.Open
 if err != nil {
 log.Fatal
 defer db.Close
复制代码

这里到 db 不支持多链接。这是因为对于 database file 一个链接保持了一个文件锁 file lock。

如果并发,后续链接会阻塞。

可以为单个链接添加 超时控制

db, err := bolt.Open
复制代码

与 google 的 levelDB 不同,bbolt 支持事务。 detail bolt 优缺点:detail 同时 bbolt 出自 bolt ,没太多不同,只是 bbolt 目前还在维护。

同时只能有

actions⚠️:在事务开始时,会保持一个数据视图 这意味着事务处理过程中不会由于别处更改而改变

单个事务和它所创建的所有对象都不是线程安全的。

建议加锁 或者 每一个 goroutine 并发都开启 一个 事务

当然,从 db 这个 bbolt 的顶级结构创建 事务 是 线程安全 的

前面提到的 读写事务 和 只读事务 拒绝相互依赖。当然也不能在同一个 goroutine 里。

死锁原因是 读写事务 需要周期性重新映射 data 文件。这在开启只读事务时是不可行的。

使用 db.Update开启一个读写事务

err := db.Update error{
 return nil
复制代码

上文提过,在一个事务中 ,数据视图是一样的。

bboltdb 根据你的返回值判断事务状态,你可以添加任意逻辑并认为出错时返回 return err bboltdb 会回滚,如果 return nil 则提交你的事务。

建议永远检查 Update 的返回值,因为他会返回如 硬盘压力 等造成事务失败的信息

⚠️:你自定义返回 error 的 error 信息同样会被传递出来。

使用 db.View 来新建一个 只读事务

err := db.View error {
 return nil
复制代码

同上,你会获得一个一致性的数据视图。

当然,只读事务 只能检索信息,不会有任何更改。

读写事务 db.Update 最后需要对 database提交更改,这会等待硬盘就绪。

每一次文件读写都是和磁盘交互。这不是一个小开销。

你可以使用 db.Batch 开启一个 批处理事务。他会在最后批量提交从而减小了开销。 ⚠️:db.Batch 只对 goroutine 起效

使用 批处理事务 需要做取舍,用 幂等函数 换取 速度 ⚠️: db.Batch 在一部分事务失败的时候会尝试多次调用那些事务函数,如果不是幂等会造成不可预知的非最终一致性。

例:使用事务外的变量来使你的日志不那么奇怪

var id uint64
err := db.Batch error {
 // Find last key in bucket, decode as bigendian uint64, increment
 // by one, encode back to []byte, and add new key.
 id = newValue
 return nil
if err != nil {
 return ...
fmt.Println
复制代码

可以手动进行事务的 开启 ,回滚,新建对象,提交等操作。因为本身 db.Update 和 db.View 就是他们的包装 ⚠️:手动事务记得 关闭

开启事务使用 db.Begin 同时参数代表着是否可以写操作。如下:

// Start a writable transaction.
tx, err := db.Begin
if err != nil {
 return err
defer tx.Rollback
// Use the transaction...
_, err := tx.CreateBucket)
if err != nil {
 return err
// Commit the transaction and check for error.
if err := tx.Commit; err != nil {
 return err
复制代码

桶是键值对的集合。在一个桶中,键值唯一。

使用 Tx.CreateBucket 和 Tx.CreateBucketIfNotExists 建立一个新桶 接受参数是 桶的名字

使用 Tx.DeleteBucket 根据桶的名字来删除

func main {
 db, err := bbolt.Open
 if err != nil {
 log.Fatal
 defer db.Close
 db.Update error {
 b, err := tx.CreateBucketIfNotExists)
 if err != nil {
 return fmt.Errorf
 if err = tx.DeleteBucket); err != nil {
 return err
 return nil
复制代码

最重要的部分,就是 kv 存储怎么使用了,首先需要一个 桶 来存储键值对。

使用Bucket.Put来存储键值对,接收两个 []byte 类型的参数

db.Update error {
 b := tx.Bucket)
 err := b.Put, []byte)
 return err
复制代码

很明显,上面的例子设置了 Pair: key:answer value:42

使用 Bucket.Get 来查询键值。参数是一个 []byte

db.View error {
 b := tx.Bucket)
 v := b.Get)
 fmt.Printf
 return nil
复制代码

细心会注意到,Get是不会返回 error 的,这是因为 Get 一定能正常工作,相应的,当返回 nil 时,查询的键值对不存在。 ⚠️:注意 0 长度的值 和 不存在键值对 的行为是不一样的。

func main {
 db, err := bolt.Open
 if err != nil {
 log.Fatal
 defer db.Close
 err = db.Update error {
 b, err := tx.CreateBucketIfNotExists)
 if err != nil {
 return fmt.Errorf
 if err = b.Put, []byte); err != nil {
 return err
 if err = b.Put, []byte); err != nil {
 return err
 return nil
 db.View error {
 b := tx.Bucket)
 v := b.Get)
 fmt.Println) // false
 fmt.Println // true
 v = b.Get)
 fmt.Println) // false
 fmt.Println // true
 return nil
复制代码

使用 Bucket.Delete 删除键值对

db.View error {
 b := tx.Bucket)
 fmt.Println))
 err := b.Delete)
 if err != nil {
 return err
 return nil
复制代码

⚠️: Get 获取到的字节切片值只在当前事务有效,如果要在其他事务中使用需要使用 copy 将其拷贝到其他的字节切片

使用 NextSequence来创建自增键,见下例

// CreateUser saves u to the store. The new user ID is set on u once the data is persisted.
func  CreateUser error {
 return s.db.Update error {
 // Retrieve the users bucket.
 // This should be created when the DB is first opened.
 b := tx.Bucket)
 // Generate ID for the user.
 // This returns an error only if the Tx is closed or not writeable.
 // That can t happen in an Update call so I ignore the error check.
 id, _ := b.NextSequence
 u.ID = int
 // Marshal user data into bytes.
 buf, err := json.Marshal
 if err != nil {
 return err
 // Persist bytes to users bucket.
 return b.Put, buf)
// itob returns an 8-byte big endian representation of v.
func itob []byte {
 b := make
 binary.BigEndian.PutUint64)
 return b
type User struct {
 ID int
复制代码

很简单的,桶可以实现嵌套存储

func  CreateBucket 
func  CreateBucketIfNotExists 
func  DeleteBucket error
复制代码

假设您有一个多租户应用程序,其中根级别存储桶是帐户存储桶。该存储桶内部有一系列帐户的序列,这些帐户本身就是存储桶。在序列存储桶中,可能有许多相关的存储桶。

// createUser creates a new user in the given account.
func createUser error {
 // Start the transaction.
 tx, err := db.Begin
 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))
 // Setup the users bucket.
 bkt, err := root.CreateBucketIfNotExists)
 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; err != nil {
 return err
 } else if err := bkt.Put), buf); err != nil {
 return err
 // Commit the transaction.
 if err := tx.Commit; err != nil {
 return err
 return nil
复制代码

在桶中,键值对根据 键 的 值是有字节序的。 使用 Bucket.Cursor对其进行迭代

db.View error {
 // Assume bucket exists and has keys
 b := tx.Bucket)
 c := b.Cursor
 for k, v := c.First; k != nil; k, v = c.Next {
 fmt.Printf
 return nil
复制代码

Cursor 有 5 种方法进行迭代

<
>
关于我们
AB模版网成立于2014年,我们是一家专注用户体验设计开发与互联网品牌建设的设计公司,创立至今为2000多位客户提供了创新与专业的设计方案。设计服务范围包括:交互原型设计、产品视觉设计、网站设计与开发建设、移动及软件产品界面设计、图标设计、品牌及平面设计等。

联系我们

13588889999服务时间:9:00-18:00)

admin@adminbuy.cn

官方微信官方微信

部门热线

前   台:13588889999
业务部:13588889999
客服部:13588889999
技术部:13566667777
人事部:13566667777

咨询电话13588889999 返回顶部
返回顶部