最新要闻
- 金融类都有什么-金融类都有什么专业 天天即时看
- 蔚来五一换电量创历史新高 车主高速薅免费电池4万块_世界今日报
- 《王者荣耀》妲己九尾“收割”女玩家:实体手办1199元
- 苹果树桃树缓控修剪新技术_关于苹果树桃树缓控修剪新技术的简介 环球微动态
- 宁波地铁全天免费坐 五一延长1小时 有站点人流暴涨200%|世界快看
- 今年的五一“疯”了吗?网友:走不动 根本走不动
- 太科幻!比亚迪深圳云巴开通:全自动运行、刷脸进站、无感支付
- AMD Yes!一文了解锐龙Z1处理器:为掌机而生 天天热头条
- 天天热头条丨让AI画“边打游戏边吃意面” AI毅然让玩家啃了手柄
- 《饥荒联机版》在线人数破纪录 Steam史低只要4.8元
- 评论区被疯狂玩梗 抖音网红女骑手:何必恶语相对
- 通讯!经典超烂游戏《Ancient Roman》引热议 日本4成女性居然都知道
- 环球关注:亚美尼亚飞一航空:未预先通知,土耳其对其关闭了领空
- 苏宁易购一季度亏损1亿元:大幅减亏90% 核心家电3C业务盈利 全球微速讯
- 快讯:景区回应“上厕所要花55元买门票”:园外无公厕
- 直线过抛物线焦点公式(抛物线焦点公式)-环球关注
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
全球热点评!详细的BoltDB学习记录文档
最近项目中用到了boltdb这个go开发的key/value 数据库,但是之前并有接触过,所以特意去看了官方,也找了些资料,网上找的资料要不就是官方文档的翻译,要不就是简单的介绍一点,都不是很全,所以这里记录下。话不多说,冲!
本篇文章是参考了官方的文档,内容和官方的基本一致,只是加了些自己的理解在里面而已,大家也可以去直接去看github上的介绍。地址:bolt。
说个实话,去看官方文档的时候,其实还是有好多没有看懂,还是需要自己去看看源码,才能明白其中官方文档说的意思。
(相关资料图)
1、BoltDB简介
本文介绍的是bolt这个数据库,并不是etcd用到的bbolt。这个一定要注意,别搞混了。
说到这个,随便提一嘴,etcd中的 bbolt 是基于bolt开发而来的,因为 bolt 2019年3月19日就封版了,不再进行维护,所以 etcd 团队就fork了一份,再此基础上进行开发维护。
1.1 BoltDB 基础介绍
BoltDB是一个纯Go语言编写的键值存储数据库,它的设计目标是提供一个简单的纯 Go key/value
存储,并且不会使代码具有多余的特性。BoltDB采用了B+树
的数据结构,支持ACID事务,具有快速的读写速度和低延迟的响应时间。BoltDB的特点包括:
1、简单易用:BoltDB的API非常简单易用,只需要几行代码就可以完成数据库的创建、打开、读写等操作。
2、高性能:BoltDB采用了B+树的数据结构,支持快速的读写操作,同时还具有较低的内存占用和CPU负载。
3、可嵌入:BoltDB可以嵌入到应用程序中,不需要单独的数据库服务器,可以方便地进行部署和管理。
4、ACID事务:BoltDB支持ACID事务,可以保证数据的一致性和可靠性。
5、支持并发:BoltDB支持并发读写操作,可以满足高并发的应用场景。
1.2 BoltDB使用场景
从上面的基础介绍我们知道,BoltDB 可以用于下面的场景中,不过具体场景具体分析,BoltDB也有很多局限性。
1、Web 应用程序的持久化存储:BoltDB 可以用于存储 Web 应用程序的配置信息、用户会话信息等。
2、分布式系统的数据存储:BoltDB 可以用于存储分布式系统的元数据、状态信息等。
3、数据分析和机器学习:BoltDB 可以用于存储数据集、模型参数等。
4、消息队列:BoltDB 可以用于存储消息队列的消息、消费者状态等。
我们现在的项目,需要开发一个客户端程序,部署到硬件设备上,部署是由项目的实施人员跟进的。考虑到客户端存储的数据类型有限,并且查询都是基于 key 进行查询,所以 BoltDB 是适合现有的场景的。
说了这么多介绍,接下来就一起学习如何使用 BoltDB 吧。
2、BoltDB 入门
2.1 BoltDB的安装
安装命令如下:
go get github.com/boltdb/bolt/...
执行该命令后,会将 Blot 可执行文件安装到 $GOBIN
路径中。
2.2 打开BlotDB
bolt.Open()
打开的数据库路径不存在时,会自动创建。注意:如果路径包含目录,目录必须存在,否则报错。
Bolt 中的顶级对象是一个 DB。它被表示为磁盘上的单个文件,表示数据的一致快照。
要打开数据库,只需使用 bolt.Open()
函数:
package mainimport ("log""github.com/boltdb/bolt")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()...}
请注意:Bolt 会在数据文件上获得一个文件锁,所以多个进程不能同时打开同一个数据库。 打开一个已经打开的 Bolt 数据库将导致它挂起,直到另一个进程关闭它。为防止无限期等待,您可以将超时选项传递给Open()
函数:
db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
3、事务
Bolt 一次只允许一个读写事务
,但是一次允许多个只读事务
。 每个事务处理都有一个始终如一的数据视图()。
单个事务以及从它们创建的所有对象(例如 bucket,key)不是线程安全的
。 要处理多个 goroutine 中的数据,则必须为每个 goroutine 启动一个事务,或使用锁来确保一次只有一个 goroutine 访问事务。 从DB 创建事务是线程安全的
。
只读事务和读写事务不应该相互依赖,一般不应该在同一个例程中同时打开。 这可能会导致死锁
,因为读写事务需要定期重新映射数据文件,但只有在只读事务处于打开状态时才能这样做。
3.1 读写事务 - db.Update
为了启动一个读写事物,你可以使用DB.Update()
函数:
err := db.Update(func(tx *bolt.Tx) error { ... return nil})
在闭包内部,有一个一致的数据库视图。 那什么时候提交事务,什么时候回滚事务呢?
- 如果返回错误,那么整个事务将回滚。
- 如果函数没有返回任何错误,则提交事务。
注意: 我们使用时,一定要检查db.Update函数
返回的错误,因为它会报告任何可能导致事务无法完成的磁盘故障。如果在闭包中返回一个错误,它将被传递。
例子:
package mainimport ("fmt""github.com/boltdb/bolt")func main() {db, err := bolt.Open("test/my.db", 0600, nil)if err != nil {fmt.Printf("open db error, err: %v\n", err)return}err = db.Update(func(tx *bolt.Tx) error {b := tx.Bucket([]byte("test"))if b == nil {b, err = tx.CreateBucket([]byte("test"))if err != nil {fmt.Printf("tx.CreateBucket failed, err: %v\n", err)return err}}_ = b.Put([]byte("name"), []byte("xiaoling"))v := b.Get([]byte("name"))fmt.Println("name: ", string(v))return nil})if err != nil {fmt.Println("db.Update failed, err:", err)}}
3.2 只读事务 - db.View
启动一个只读事务,你可以使用DB.View()
函数:
err := db.View(func(tx *bolt.Tx) error { ... return nil})
可以在此闭包中获得数据库的一致视图,但是,在只读事务中不允许进行更新、修改、删除操作。只能检索存储区,检索值,或者在只读事务中复制数据库。
例子:
package mainimport ("errors""fmt""github.com/boltdb/bolt")func main() {db, err := bolt.Open("test/my.db", 0600, nil)if err != nil {fmt.Printf("open db error, err: %v\n", err)return}err = db.View(func(tx *bolt.Tx) error {b := tx.Bucket([]byte("test"))if b == nil {return errors.New("not found Bucket: test")}v := b.Get([]byte("name"))fmt.Println("name: ", string(v))return nil})if err != nil {fmt.Println("db.View failed, err:", err)}}
3.3 批量读写事务 - db.Batch
每个 DB.Update()
等待磁盘提交写入。我们知道,一条一条数据的插入是比较耗时的,性能肯定不会太高,那有没有像Mysql那样批量插入的方式呢?
DB.Batch()
可以实现上面的需求,最小化这种开销,下面一起来看看如何使用。
err := db.Batch(func(tx *bolt.Tx) error { ... return nil})
并发批量调用可以组合成更大的交易。 批处理仅在有多个 goroutine 调用时才有用。
如果部分事务失败,batch 可以多次调用给定的函数。 该函数必须是幂等的,只有在成功从 DB.Batch()
返回后才能生效。
注意:
看官方文档到这里时,一脸懵逼,说了个啥啊(原谅自己太菜)。
接下来说说自己的理解。如果有错误的地方,恳请指出,轻喷!
官方的意思是DB.Update()
要等到把数据提交到磁盘后才会返回,那如果程序中有大量的DB.Update()
等待执行,每个DB.Update()
都要等待把数据提交到磁盘后才执行,效率不高。哪有什么方式可以优化呢,答案就是: DB.Batch()
。
DB.Batch() 有机会把多个 fn 函数提交到一组,然后一起执行,这个机会是什么意思呢?
在 10 毫秒(默认值)内通过 Batch() 提交的函数,就有可能组成一组,一起执行。
DB.Batch()
源码:
func (db *DB) Batch(fn func(*Tx) error) error {errCh := make(chan error, 1)db.batchMu.Lock()if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) {// There is no existing batch, or the existing batch is full; start a new one.db.batch = &batch{db: db,}// 最多等待 MaxBatchDelay ,默认值为 10 * time.Milliseconddb.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger)}// 重点看这里,这就是把 传进来的 fn 函数追加到 db.batch.calls// 当 calls 中的数量 大于 MaxBatchSize 时就提交事务,写入磁盘// 注意,那如果数量不大于怎么办呢,不要慌,db.batch.timer 后会自动执行 fn 函数db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh}) // MaxBatchSize 默认值为 1000if len(db.batch.calls) >= db.MaxBatchSize {// wake up batch, it"s ready to rungo db.batch.trigger()}db.batchMu.Unlock()err := <-errChif err == trySolo {err = db.Update(fn)}return err}
看了上面源码过后,对DB.Batch()
有了一定的了解,接下来,再通过例子进行验证,就知道官方的意思了。
package mainimport ("errors""fmt""github.com/boltdb/bolt""time")func main() {db, err := bolt.Open("test/my.db", 0600, nil)if err != nil {fmt.Printf("open db error, err: %v\n", err)return}loopCount := 1db.MaxBatchSize = 2db.MaxBatchDelay = time.Second * 3for i := 0; i < loopCount; i++ {i := igo func() {err = db.Batch(func(tx *bolt.Tx) error {b := tx.Bucket([]byte("test"))if b == nil {return errors.New("not found Bucket: test")}b.Put([]byte("name-1"), []byte("xiaolin-1"))fmt.Println(string(b.Get([]byte("name-1"))))return nil})if err != nil {fmt.Println(i, "---> db.Batch failed, err:", err)}}()}if err != nil {fmt.Println("db.Update failed, err:", err)}time.Sleep(time.Second*2)}
这里,我们通过loopCount、MaxBatchSize、MaxBatchDelay、time.Sleep(time.Second*2)
不同的值来说明:
1、第一种情况
loopCount := 1db.MaxBatchSize = 2db.MaxBatchDelay = time.Second * 3time.Sleep(time.Second*2)
运行结果为空。为什么呢,因为 我们只往 db.batch.calls
中添加了一个Call
,所以会等待3秒钟后才会提交数据到磁盘,但是程序2秒钟之后就结果了。所以什么都没有。
2、第二种情况
loopCount := 2db.MaxBatchSize = 2db.MaxBatchDelay = time.Second * 3time.Sleep(time.Second*2)
运行结果打印了两次 xiaolin-1。为什么呢,因为 我们往 db.batch.calls
中添加了2个Call
,大于等于MaxBatchSize
,会立即把数据提交到磁盘。
3、第三种情况
loopCount := 1db.MaxBatchSize = 2db.MaxBatchDelay = time.Second * 3time.Sleep(time.Second*4)
运行结果是 等待了3秒钟后,打印了xiaolin-1
,然后 等待了1秒,程序结果。为什么呢,因为 我们往 db.batch.calls
中添加了1个Call
,小于MaxBatchSize
,这个时候就会等待MaxBatchDelay
才会执行 db.batch.trigger
。
通过上面三种情况,大致清楚了 DB.Batch()
的作用。到这里也终于明白Batch函数注释,Batch is only useful when there are multiple goroutines calling it.
这句话的含义了。
3.4 手动管理交易
DB.View()
和 DB.Update()
函数是DB.Begin()
函数的包装器。 这些帮助函数将启动事务,执行一个函数,然后在返回错误时安全地关闭事务。 这是使用 Bolt 事务的推荐方式。
但是,有时可能需要手动开始和结束事务。 可以直接使用DB.Begin()
函数,一定记得关闭事务。
// 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}
DB.Begin()
的第一个参数是一个布尔值,说明事务是否可写,这也是DB.View()
和 DB.Update()
的区别之一。
4、bolt的使用
4.1 Bucket的管理
在上面的使用案例中,我们都会先通过tx.CreateBucket
或者tx.Bucket
来获取一个Bucket
对象,为什么呢?
因为Bucket
是数据库中 key/value 对的集合。 bucket 中的所有 key 必须是唯一的。 可以使用 DB.CreateBucket()
函数创建一个存储 bucket:
db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket([]byte("MyBucket")) if err != nil { return fmt.Errorf("create bucket: %s", err) } return nil})
也可以使用 Tx.CreateBucketIfNotExists()
函数创建一个 bucket ,当然,如果存在就不会创建。 在实际编程中,我们通常使用此函数来获取或者创建Bucket
,其实我们通过看源码,会发现Tx.CreateBucketIfNotExists
也是对tx.CreateBucket
和tx.Bucket
做了一层封装而已。
要删除一个 bucket,只需调用 Tx.DeleteBucket()
函数即可。
4.2 如何往Bucket中读写数据
如何将 key/value 对保存到 bucket呢,需要 Bucket.Put()
函数:
db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("MyBucket")) err := b.Put([]byte("answer"), []byte("42")) return err})
这将在 MyBucket 中存储一个key为 “answer” ,value为“42”的key/value对。
要检索这个value,我们可以使用 Bucket.Get()
函数:
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()
函数不会返回错误,因为它的操作保证可以正常工作(除非有某种系统失败)。 如果 key 存在,则它将返回其字节片段值。 如果不存在,则返回零。 请注意,可以将零长度值设置为与不存在的键不同的键
。
使用 Bucket.Delete()
函数从 bucket 中删除一个 key。
请注意,从 Get()
返回的值仅在事务处于打开状态时有效。 如果需要在事务外使用到它,则必须使用 copy()
将其复制到另一个字节片段。
上面这句话咋个理解呢,我是没看到什么意思!!!!我说说我的理解,如果错误了,请不吝赐教,谢谢。我感觉这里的意思:如果
db.Update
函数外部需要使用到Get()
查询得到的值,需要先使用 copy 函数将值复制给外部的变量。因为slice
是引用类型,这样当我们在db.Update
函数内容修改了返回的值时,不会影响到最开查询的结果。
4.3 自动递增函数-NextSequence()
我们知道Mysql可以将主键设置为自动递增的,那bolt有没有也是的功能呢,是有的,Bucket.NextSequence()
将返回一个自动递增的数字。
package mainimport ("fmt""github.com/boltdb/bolt""sync")func main() {db, err := bolt.Open("test/my.db", 0600, nil)if err != nil {fmt.Printf("open db error, err: %v\n", err)return}wg := sync.WaitGroup{}testMap := make(map[uint64]struct{})for i := 0; i < 100; i++ {wg.Add(1)go func() {defer func() {wg.Done()}()db.Update(func(tx *bolt.Tx) error {b := tx.Bucket([]byte("test"))id, _ := b.NextSequence()v, ok := testMap[id]if ok {fmt.Printf("%v in testMap \n", v)}testMap[id] = struct{}{}return nil})}()}if err != nil {fmt.Println("db.Update failed, err:", err)}wg.Wait()if len(testMap) != 100 {var note interface{} = "testMap exist duplicate key"panic(note)}}
5、 如何迭代查询 keys
5.1 基础介绍
Bolt 将 keys 以字节排序的顺序
存储在一个 bucket 中。这使得对这些 keys 的顺序迭代非常快。要遍历 key,我们将使用一个 Cursor:
package mainimport ("bytes""fmt""github.com/boltdb/bolt")func main() {db, err := bolt.Open("test/my.db", 0600, nil)if err != nil {fmt.Printf("open db error, err: %v\n", err)return}err = db.View(func(tx *bolt.Tx) error {// Assume bucket exists and has keysc := tx.Bucket([]byte("test")).Cursor()prefix := []byte("name")for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() {fmt.Printf("key=%s, value=%s\n", k, v)}return nil})if err != nil {fmt.Println("db.Update failed, err:", err)}}
结果:
key=name, value=xiaolingkey=name-0, value=0key=name-1, value=xiaolin-1key=name-10, value=10key=name-2, value=xiaolin-2key=name-3, value=xiaolin-3key=name-4, value=4key=name-5, value=5key=name-6, value=6key=name-7, value=7key=name-8, value=8key=name-9, value=9
Cursor 允许您移动到键列表中的特定点,并一次向前或向后移动一个键。
Cursor 上有以下功能:
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 [] byte,value [] byte)的返回签名。 当你迭代到游标的末尾时,Next()
将返回一个零键。 在调用 Next()
或 Prev()
之前,您必须使用 First()
, Last()
或 Seek()
来寻找位置。 如果你不寻找位置,那么这些函数将返回一个零键。
在迭代期间,如果 key 非零,但是 value 为零,则意味着 key 指的是一个 bucket 而不是一个 value。 使用 Bucket.Bucket()
访问子 bucket。通过下面这个例子来进行理解:
package mainimport ("fmt""github.com/boltdb/bolt")func main() {db, err := bolt.Open("test/my.db", 0600, nil)if err != nil {fmt.Printf("open db error, err: %v\n", err)return}err = db.Update(func(tx *bolt.Tx) error {// Assume bucket exists and has keysb := tx.Bucket([]byte("test"))b2, _ := b.CreateBucketIfNotExists([]byte("test2"))b2.Put([]byte("name"), []byte("nihao"))c := b.Cursor()prefix := []byte("name")for k, v := c.Seek(prefix); k != nil ; k, v = c.Next() {if k != nil && v == nil { // 重点就是这句话,这种情况下说明key 指的是一个 bucket 而不是一个 valueb2 = b.Bucket(k)fmt.Println(string(b2.Get([]byte("name"))))}}return nil})if err != nil {fmt.Println("db.Update failed, err:", err)}}// 执行结果nihao
5.2 前缀扫描
迭代关键字前缀,可以将 Seek()
和 bytes.HasPrefix()
组合起来:
db.View(func(tx *bolt.Tx) error {// Assume bucket exists and has keysc := 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", k, v)}return nil})
5.3 范围扫描
另一个常见的用例是扫描一个范围,如时间范围。我们可以使用可排序的时间编码(如 RFC3339 ),那么可以查询特定的日期范围,如下所示:
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", k, v)}return nil})
请注意,尽管 RFC3339 是可排序的,但 RFC3339Nano 的 Golang 实现不会在小数点后使用固定数量的数字,因此无法排序。
5.4 ForEach()
那怎么迭代查询 bucket 中的所有key呢,方法是使用ForEach()
函数。
db.View(func(tx *bolt.Tx) error {// Assume bucket exists and has keysb := tx.Bucket([]byte("MyBucket"))b.ForEach(func(k, v []byte) error {fmt.Printf("key=%s, value=%s\n", k, v)return nil})return nil})
请注意,ForEach()
中的键和值仅在事务处于打开状态时有效。如果您需要使用事务外的键或值,则必须使用 copy()
将其复制到另一个字节片。
6、嵌套的 bucket
我们可以在一个 key 中存储一个 bucket 来创建嵌套的 bucket 。 创建方式和以前创建 bucket 一样:
func (*Bucket) CreateBucket(key []byte) (*Bucket, error)func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error)func (*Bucket) DeleteBucket(key []byte) error
这里讲一个嵌套使用的bucket的场景,假设有一个多租户应用程序,其中根级 bucketRoot 是帐户 bucket。 这个 bucketRoot 里面是一系列的帐户,这些账户本身就是一个 bucket。 而在序列的 bucket 中,存储各个账户的信息。
// createUser creates a new user in the given account.func createUser(accountID 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([]byte(strconv.FormatUint(accountID, 10))) // Setup the users bucket. bkt, err := root.CreateBucketIfNotExists([]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([]byte(strconv.FormatUint(u.ID, 10)), buf); err != nil { return err } // Commit the transaction. if err := tx.Commit(); err != nil { return err } return nil}
7、数据库备份
Blot 是一个单一的文件,所以很容易备份。 您可以使用Tx.WriteTo()
函数将数据库的一致视图写入目的地。 如果从只读事务中调用它,它将执行热备份而不会阻止其他数据库的读写操作
。
默认情况下,它将使用一个常规的文件句柄来利用操作系统的页面缓存。 如何针对大于RAM的数据集进行优化,请参阅Tx文档。
如果需要处理大型数据集,需要采取特殊的优化措施,这些措施可以在Tx文档中找到。
一个常见的用例是通过 HTTP 进行备份,因此可以使用像 curl 这样的工具来进行数据库备份:
func BackupHandleFunc(w http.ResponseWriter, req *http.Request) { err := db.View(func(tx *bolt.Tx) error { w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", `attachment; filename="my.db"`) w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) _, err := tx.WriteTo(w) return err }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) }}复制代码
那么你可以用这个命令备份:
$ curl http://localhost/backup > my.db
或者你可以打开你的浏览器到http://localhost/backup
,它会自动下载。如果你想备份到另一个文件,你可以使用 Tx.CopyFile()
辅助函数。
8、官方文档其他的介绍
上面的内容基本上是关于如何使用 bolt 数据库的,下面的内容则是讲一讲 bolt 与其他数据的区别,以及什么时候选择使用 bolt 有优势。下面的内容就是复制的参考链接的内容了,嘿嘿。
8.1 与其他数据库比较
8.1.1 Postgres, MySQL, & other relational databases
关系数据库将数据结构化为行,并且只能通过使用SQL来访问。 这种方法提供了如何存储和查询数据的灵活性,但也会导致解析和规划SQL语句的开销。 Bolt通过字节切片键访问所有数据。 这使得 Bolt 可以快速读写数据,但不提供内置的连接值的支持。
大多数关系数据库(SQLite除外)都是独立于服务器的独立服务器。 这使您的系统可以灵活地将多个应用程序服务器连接到单个数据库服务器,但是也增加了通过网络序列化和传输数据的开销。 Bolt 作为应用程序中包含的库运行,因此所有数据访问都必须通过应用程序的进程。 这使数据更接近您的应用程序,但限制了多进程访问数据。
8.1.2 LevelDB, RocksDB
LevelDB 及其衍生产品(RocksDB,HyperLevelDB)与 Bolt 类似,它们被绑定到应用程序中,但是它们的底层结构是日志结构合并树(LSM树)。 LSM 树通过使用提前写入日志和称为 SSTables 的多层排序文件来优化随机写入。 Bolt 在内部使用 B+ 树,只有一个文件。两种方法都有折衷。
如果您需要高随机写入吞吐量(> 10,000 w / sec)或者您需要使用旋转磁盘,则 LevelDB可能是一个不错的选择。 如果你的应用程序是重读的,或者做了很多范围扫描,Bolt 可能是一个不错的选择。
另一个重要的考虑是 LevelDB 没有交易。 它支持批量写入键/值对,它支持读取快照,但不会使您能够安全地进行比较和交换操作。 Bolt 支持完全可序列化的 ACID 事务。
8.1.3 LMDB
Bolt 最初是 LMDB 的一个类似实现,所以在结构上是相似的。 两者都使用 B+ 树,具有完全可序列化事务的 ACID 语义,并且使用单个writer 和多个 reader 来支持无锁 MVCC。
这两个项目有些分歧。 LMDB 主要关注原始性能,而 Bolt 专注于简单性和易用性。 例如,LMDB 允许执行一些不安全的操作,如直接写操作。 Bolt 选择禁止可能使数据库处于损坏状态的操作。 这个在 Bolt 中唯一的例外是 DB.NoSync。
API 也有一些差异。 打开 mdb_env 时 LMDB 需要最大的 mmap 大小,而Bolt将自动处理增量式 mmap 大小调整。 LMDB 用多个标志重载 getter 和 setter 函数,而 Bolt 则将这些特殊的情况分解成它们自己的函数。
8.2 注意事项和限制
选择正确的工具是非常重要的,Bolt 也不例外。在评估和使用 Bolt 时,需要注意以下几点:
- Bolt 适合读取密集型工作负载。顺序写入性能也很快,但随机写入可能会很慢。您可以使用
DB.Batch()
或添加预写日志来帮助缓解此问题。 - Bolt在内部使用B +树,所以可以有很多随机页面访问。与旋转磁盘相比,SSD可显着提高性能。
- 尽量避免长时间运行读取事务。 Bolt使用
copy-on-write
技术,旧的事务正在使用,旧的页面不能被回收。 - 从 Bolt 返回的字节切片只在交易期间有效。 一旦事务被提交或回滚,那么它们指向的内存可以被新页面重用,或者可以从虚拟内存中取消映射,并且在访问时会看到一个意外的故障地址恐慌。
- Bolt在数据库文件上使用独占写入锁,因此不能被多个进程共享
- 使用
Bucket.FillPercent
时要小心。设置具有随机插入的 bucket 的高填充百分比会导致数据库的页面利用率很差。 - 一般使用较大的 bucket。较小的 bucket 会导致较差的页面利用率,一旦它们大于页面大小(通常为4KB)。
- 将大量批量随机写入加载到新存储区可能会很慢,因为页面在事务提交之前不会分裂。不建议在单个事务中将超过 100,000 个键/值对随机插入单个新 bucket中。
- Bolt使用内存映射文件,以便底层操作系统处理数据的缓存。 通常情况下,操作系统将缓存尽可能多的文件,并在需要时释放内存到其他进程。 这意味着Bolt在处理大型数据库时可以显示非常高的内存使用率。 但是,这是预期的,操作系统将根据需要释放内存。 Bolt可以处理比可用物理RAM大得多的数据库,只要它的内存映射适合进程虚拟地址空间。 这在32位系统上可能会有问题。
- Bolt数据库中的数据结构是存储器映射的,所以数据文件将是endian特定的。 这意味着你不能将Bolt文件从一个小端机器复制到一个大端机器并使其工作。 对于大多数用户来说,这不是一个问题,因为大多数现代的CPU都是小端的。
- 由于页面在磁盘上的布局方式,Bolt无法截断数据文件并将空闲页面返回到磁盘。 相反,Bolt 在其数据文件中保留一个未使用页面的空闲列表。 这些免费页面可以被以后的交易重复使用。 由于数据库通常会增长,所以对于许多用例来说,这是很好的方法 但是,需要注意的是,删除大块数据不会让您回收磁盘上的空间。
参考链接:
bolt-github官网
[译]boltDB
关键词:
PTA题目集4~6总结
全球热点评!详细的BoltDB学习记录文档
看点:OO题目集4-6总结
金融类都有什么-金融类都有什么专业 天天即时看
“五一”迎来客流高峰 各部门全力保障旅客出行
蔚来五一换电量创历史新高 车主高速薅免费电池4万块_世界今日报
《王者荣耀》妲己九尾“收割”女玩家:实体手办1199元
苹果树桃树缓控修剪新技术_关于苹果树桃树缓控修剪新技术的简介 环球微动态
宁波地铁全天免费坐 五一延长1小时 有站点人流暴涨200%|世界快看
今年的五一“疯”了吗?网友:走不动 根本走不动
太科幻!比亚迪深圳云巴开通:全自动运行、刷脸进站、无感支付
AMD Yes!一文了解锐龙Z1处理器:为掌机而生 天天热头条
天天热头条丨让AI画“边打游戏边吃意面” AI毅然让玩家啃了手柄
要闻速递:性能_1 Jmeter脚本编写
《饥荒联机版》在线人数破纪录 Steam史低只要4.8元
评论区被疯狂玩梗 抖音网红女骑手:何必恶语相对
通讯!经典超烂游戏《Ancient Roman》引热议 日本4成女性居然都知道
环球关注:亚美尼亚飞一航空:未预先通知,土耳其对其关闭了领空
stable-diffusion-webui 环境设置过程记录 每日速读
苏宁易购一季度亏损1亿元:大幅减亏90% 核心家电3C业务盈利 全球微速讯
快讯:景区回应“上厕所要花55元买门票”:园外无公厕
直线过抛物线焦点公式(抛物线焦点公式)-环球关注
九年不屈!贾跃亭的FF 91量产下线 FF:工艺品质媲美豪华车制造商
探索“研学+”模式助力乡村振兴 张家港乐余镇启动乡村研学旅游季 当前最新
世界焦点!关于Linux操作系统使用lastlog命令对OS账号最后一次登录时间的审计
【世界独家】如何彻底关闭 Windows 10 自动更新
AITO问界卖的多亏得多?赛力斯:2022年净亏损超38亿元
微软690亿美元收购案被阻 动视暴雪CEO:必须合并才能跟腾讯、字节竞争|焦点滚动
西湖边最美人墙2.0版来了:为大家出行保驾护航_报资讯
视讯!中欧班列(武汉)连续4个月单月开行百列以上 同比增长近200%
赵长江:清华经管EMBA校友 一次下单超过20台腾势D9 动态
玩家建议微软推出自己的掌机:性能和XSS一样就行
你知道吗?火星上有22个中国地名:西柏坡、古田、窑店等
电影《长空之王》票房破2亿 王一博肩膀出血也不吭声:被赞爷们气 天天热闻
【全球热闻】襄阳公安这场推进会透出妥妥“安全感”
商务部:“五一”假期首日消费市场大幅升温
天天要闻:鲁绣、丝绸织染等山东非遗“炫技”海南
焦点快报!这价格疯了!24寸显示器仅279元:还是三微边设计
让《灌篮高手》电影被喷的“三渲二”技术 可以做到多牛X?_报道
CPU处理器价格越来越贵 笔记本除外:大跌9% 全球观热点
环球今热点:五一旅游闹心 115元买4.6斤水果少一斤后续:情况属实 顶格处罚
1999元起卖爆!Redmi Note 12 Turbo首销月三个销量第一:打破行业记录|全球快资讯
.Net Core Console&Cache 当前动态
共赴数字之约 描绘数字未来
当前观察:RTX 4070为何会破发?背后原因揭开
热门看点:ChatGPT一炮而红 OpenAI再度融资:估值已达2000亿
冲击30℃!五一假期北方多地开启升温模式 南方降雨在路上 世界今亮点
聊聊拍照那些事:五一外出你更喜欢用相机还是手机?_每日速读
越级影像旗舰真我11即将发布 realme徐起:五一大家休息 节后爆猛料|全球即时看
全球微资讯!“五一”档10余部新片集中上映
住客称入住网红酒店发现死尸打1星“差评” 涉事酒店回应_微头条
每日热讯!外观大变 首增插混!全新广汽本田雅阁开启11元盲订
曝Redmi K60 Ultra下半年登场:天玑9200+加持 安卓CPU性能之王
比NAS更好用!华为家庭存储便宜了:2699元起 最大16TB 环球热讯
两款《古墓丽影》游戏将登陆Switch!跟着劳拉去破坏“文物”
学系统集成项目管理工程师(中项)系列15_质量管理 世界视讯
热门5G手机市占率排名:华为P40 Pro国产机第一 5G平分天下 天天观速讯
承认车辆侧面碰撞试验“造假” 丰田章男道歉:我们错了
世界实时:五一2.4亿人次出游 酒店民宿价格5-7倍上涨 经济日报:违约应赔偿
离离原上谱!求职者:6000工资太高 只要2000可以吗?|世界时快讯
五一假期首日服务区充电站排队时长榜来了:充电要等2.7小时你接受吗?
驻村故事(逐梦)
【全球聚看点】好评率近100%!Redmi Note 12 Turbo把竞品甩在后面:1TB版太受欢迎
Markdown表格-换行、合并单元格
消息!关于方正证券的一些信息
《死或生:女神假期》新女神艾米登场:身材出众的18岁研究生 焦点要闻
超大额优惠券手慢无:植贝名朵防晒小光圈到手19.9元/瓶
聚焦产业建圈强链 成都市温江区举行校地合作交流会
【焦点热闻】退市三板行情_退市三板股票代码
启动“三年共富行动计划”,以“三拼”之势打造“六个乾潭”|微动态
蓝牙为啥叫“蓝”牙?就不能叫白牙吗?-全球微速讯
高速上44辆车被扎胎!初查结果:系货车铁屑散落
PC玩家很受伤!今年翻车的大作太多了
世界快看点丨【模板方法设计模式详解】C/Java/JS/Go/Python/TS不同语言实现
全球观热点:工程建设的范围是什么_工程建设的范围
海底捞一门店睡满人导致无法用餐:全是“特种兵”式大学生
[0基础学R语言] 软件下载与环境配置_天天热点
焦点!15 设备类型与设备驱动
万物皆有灵下一句是什么_万物皆有灵 全球百事通
每日快看:对标《原神》!《王者荣耀世界》新实机画面:蓝BUFF化身寒冰之王
Uzi老对手官宣转型!LOL“欧成”Rekkles改打辅助
快资讯丨苏教版二年级上册数学期中试卷含答案_苏教版二年级上册数学期中试卷
当前动态:4999元 荣耀MagicBook 14 GT版上架:RTX 2050独显
世界短讯!USB:终于一统江湖
视频会议的免费时代:宣告结束了 环球观热点
环球新动态:世界第八大奇迹!放假第一天兵马俑游客突破五万人:秦俑打上了保湿抗皱针
世界资讯:ssti注入
简讯:椰子怎么喝 椰子怎么喝最好
《角斗士》旧情人欣然返场
主机用质量模式玩《星球大战绝地》更好?起码可稳30帧 全球热门
MySQL常用数据库语句 天天观天下
穿小椰子怎么搭配衣服 穿小椰子鞋如何搭配_全球今日讯
最新:男子服务区买15元汉堡只有2片面包 老板:你不要我退钱
九号平衡车L8奥特曼定制版发布:首发1799元 可触发彩蛋
赛力斯2022年营收翻倍:问界M5/M7立大功
当前滚动:xss-labs靶场
男人对你说子非鱼是什么意思_子非鱼是什么意思
飞利浦推出新款24.5寸游戏显示器:280Hz高刷、0.5ms极速响应|全球观速讯
速读:14 进程的等待与唤醒机制
【环球新视野】基于python实现将AWS-ElastiCache-的Reserved_Cache_Nodes-预留节点及费用的信息统计