GolangNote

Golang笔记

bolt 数据库的事务

Permalink

Bolt 类似于 LMDB,这个被认为是在现代 kye/value 存储中最好的。但是又不同于 LevelDB,BoltDB 支持完全可序列化的ACID事务

打开数据库文件:

Go: 打开boltdb数据库文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
    "log"

    "github.com/boltdb/bolt"
)

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

    // ...
}

使用 DB.Update() 函数来进行读-写事务:

Go: 读-写事务
1
2
3
4
err := db.Update(func(tx *bolt.Tx) error {
    ...
    return nil
})

在函数内部你可以 get/set 数据和处理 error 。如果返回为 nil,事务就会从数据库得到一个 commit,但是如果你返回一个实际的错误,则会做回滚,你在函数中做的任何事情都不会 commit 到磁盘上。

使用db.View函数来做只读事务:

Go: 只读事务
1
2
3
4
err := db.View(func(tx *bolt.Tx) error {
    // ... read only
    return nil
})

Bolt 是一个 k/v 的存储并提供了一个映射表,这意味着你可以通过 name 拿到值,就像 Go 原生的 map,但是另外因为 key 是有序的,所以你可以通过 key 来遍历。

这里还有另外一层:k-v 存储在 bucket 中,你可以将 bucket 当做一个 key 的集合或者是数据库中的表。(buckets中可以包含其他的buckets)

你可以通过下面方法 update 数据库:

Go: update 数据库
1
2
3
4
5
6
7
db.Update(func(tx *bolt.Tx) error {
    b, err := tx.CreateBucketIfNotExists([]byte("posts"))
    if err != nil {
        return err
    }
    return b.Put([]byte("2015-01-01"), []byte("My New Year post"))
})

读取数据:

Go: boltdb 读取数据
1
2
3
4
5
6
db.View(func(tx *bolt.Tx) error {
    b := tx.Bucket([]byte("posts"))
    v := b.Get([]byte("2015-01-01"))
    fmt.Printf("%sn", v)
    return nil
})

使用buckets:

Go: 使用buckets
1
2
3
4
5
6
7
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
})

简单获取自增id:

Go: 获取自增id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// CreateUser saves u to the store. The new user ID is set on u once the data is persisted.
func (s *Store) CreateUser(u *User) error {
    return s.db.Update(func(tx *bolt.Tx) error {
        // Retrieve the users bucket.
        // This should be created when the DB is first opened.
        b := tx.Bucket([]byte("users"))

        // 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(id)

        // Marshal user data into bytes.
        buf, err := json.Marshal(u)
        if err != nil {
            return err
        }

        // Persist bytes to users bucket.
        return b.Put(itob(u.ID), buf)
    })
}

// itob returns an 8-byte big endian representation of v.
func itob(v int) []byte {
    b := make([]byte, 8)
    binary.BigEndian.PutUint64(b, uint64(v))
    return b
}

type User struct {
    ID int
    ...
}

批量读写事务 :

Go: 批量读写事务
1
2
3
4
err := db.Batch(func(tx *bolt.Tx) error {
    ...
    return nil
})

遍历键:

Go: 遍历键
1
2
3
4
5
6
7
8
9
10
11
12
db.View(func(tx *bolt.Tx) error {
    // Assume bucket exists and has keys
    b := tx.Bucket([]byte("MyBucket"))

    c := b.Cursor()

    for k, v := c.First(); k != nil; k, v = c.Next() {
        fmt.Printf("key=%s, value=%s\n", k, v)
    }

    return nil
})

和游标相关的函数:

Go: 和游标相关的函数
1
2
3
4
5
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.

键前缀查找:

Go: 键前缀查找
1
2
3
4
5
6
7
8
9
10
11
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); bytes.HasPrefix(k, prefix); k, v = c.Next() {
        fmt.Printf("key=%s, value=%s\n", k, v)
    }

    return nil
})

键范围扫描:

Go: 键范围扫描
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
})

使用ForEach() 函数来遍历bucket

Go: 遍历bucket
1
2
3
4
5
6
7
8
9
10
db.View(func(tx *bolt.Tx) error {
    // Assume bucket exists and has keys
    b := tx.Bucket([]byte("MyBucket"))

    b.ForEach(func(k, v []byte) error {
        fmt.Printf("key=%s, value=%s\n", k, v)
        return nil
    })
    return nil
})

嵌套buckets

Go: 嵌套buckets
1
2
3
func (*Bucket) CreateBucket(key []byte) (*Bucket, error)
func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error)
func (*Bucket) DeleteBucket(key []byte) error

使用Tx.WriteTo() 备份bolt 数据:

Go: 备份bolt 数据
1
2
3
4
5
6
7
8
9
10
11
12
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)
    }
}

可以实现在线热备份

Bash: 热备份
1
$ curl http://localhost/backup > my.db

也可以用 Tx.CopyFile() 函数来 copy 一个备份文件。

用只读模式打开数据库:

Go: 只读模式
1
2
3
4
5
db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true})
// db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second}) // 指定超时时间
if err != nil {
    log.Fatal(err)
}

使用 Bolt 的命令行

BoltDB 提供了一个名叫 bolt 的命令行工具,你可以列出 buckets 和 keys、检索values、一致性检验。

用法可以使用--help查看。

例如,检查 blog.db 数据库的一致性,核对每个页面: bolt check blog.db

本文网址: https://golangnote.com/topic/128.html 转摘请注明来源

Related articles

用LevelDB 作为地理数据库

用Goleveldb 来作地理数据库,用到 Goleveldb、Geohash。利用leveldb 的byte ordered 特性可以做高效查询。...

bolt 使用示例

bolt 是一款高性能的key value 数据库,下面是它的使用示例:...

Write a Comment to "bolt 数据库的事务"

Submit Comment Login
Based on Golang + fastHTTP + sdb | go1.17 Processed in 1ms