BoltDB 使用入门实践

BoltDB 使用入门实践Abser CatTechCats 成员/朋克程序员:看看,这就叫专业Getting Started安装go get go.etcd.io/bbolt/...会 get 两项go package -> $GOPATHbolt command line -> $GOBINOpen Database使用 kv 数据库都很简单,只需要一个文...
摘要由CSDN通过智能技术生成

BoltDB 使用入门实践

Abser Cat

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

Getting Started

安装

go get go.etcd.io/bbolt/...

会 get 两项

  1. go package -> $GOPATH
  2. bolt command line -> $GOBIN

Open Database

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

package main

import (
    "log"

    bolt "go.etcd.io/bbolt"
)

func main() {
    // Open the my.db data file in your current directory.
    // It will be created if it doesn't exist.
    db, err := bolt.Open("my.db", 0600, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    ...
}

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

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

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

db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})

Transaction

本文无关

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

事务

并发读写

同时只能有

  • 一个读写事务
  • 多个只读事务

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

线程安全

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

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

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

死锁

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

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

读写事务

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

err := db.Update(func(tx *bolt.Tx) error{
    ···
    return nil
})

上文提过,在一个事务中 ,数据视图是一样的。 (详细解释就是,在这个函数作用域中,数据对你呈现最终一致性)

返回值

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

建议永远检查 Update 的返回值,因为他会返回如 硬盘压力 等造成事务失败的信息(这是在你的逻辑之外的情况)

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

只读事务

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

err := db.View(func(tx *bolt.Tx) error {
    ···
    return nil
})

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

当然,只读事务 只能检索信息,不会有任何更改。(btw,但是你可以 copy 一个 database 的副本,毕竟这只需要读数据)

批量读写事务

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

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

你可以使用 db.Batch 开启一个 批处理事务。他会在最后批量提交(其实是多个 goroutines 开启 db.Batch 事务时有机会合并之后一起提交)从而减小了开销。 ⚠️:db.Batch 只对 goroutine 起效

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

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

var id uint64
err := db.Batch(func(tx *bolt.Tx) 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("Allocated ID %d", id)

手动事务

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

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

  • true - 读写事务
  • false - 只读事务
// Start a writable transaction.
tx, err := db.Begin(true)
if err != nil {
    return err
}
defer tx.Rollback()

// Use the transaction...
_, err := tx.CreateBucket([]byte("MyBucket"))
if err != nil {
    return err
}

// Commit the transaction and check for error.
if err := tx.Commit(); err != nil {
    return err
}

Using Store ?

Using Buckets

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

创建

使用 Tx.CreateBucket()Tx.CreateBucketIfNotExists() 建立一个新桶(推荐使用第二个) 接受参数是 桶的名字

删除

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

例子

func main() {
    db, err := bbolt.Open("./data", 0666, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    db.Update(func(tx *bbolt.Tx) error {
        b, err := tx.CreateBucketIfNotExists([]byte("MyBucket"))
        if err != nil {
            return fmt.Errorf("create bucket: %v", err)
        }

        if err = tx.DeleteBucket([]byte("MyBucket")); err != nil {
            return err
        }

        return nil
    })

}

Using key/value pairs ?

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

Put

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

db.Update(func(tx *bolt.Tx) error {
    b := tx.Bucket([]byte("MyBucket"))
    err := b.Put([]byte("answer"), []byte("42"))
    return err
})

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

Get

使用 Bucket.Get() 来查询键值。参数是一个 []byte(别忘了这次我们只是查询,可以使用 只读事务)

db.View(func(tx *bolt.Tx) error {
    b := tx.Bucket([]byte("MyBucket"))
    v := b.Get([]byte("answer"))
    fmt.Printf("The answer is: %s\n", v)
    return nil
})

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

func main() {
    db, err := bolt.Open("./data.db", 0666, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    err
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值