bboltdb 的使用技巧

Tricks

桶的自增鍵

使用 NextSequence()來創建自增鍵,見下例

// 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
    ...
}

嵌套桶

很簡單的,桶可以實現嵌套存儲

func (*Bucket) CreateBucket(key []byte) (*Bucket, error)
func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error)
func (*Bucket) DeleteBucket(key []byte) error

例子

假設您有一個多租戶應用程序,其中根級別存儲桶是帳戶存儲桶。該存儲桶內部有一系列帳戶的序列,這些帳戶本身就是存儲桶。在序列存儲桶(子桶)中,可能有許多相關的存儲桶(Users,Note等)。

// 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
}

遍歷鍵值

在桶中,鍵值對根據 鍵 的 值是有字節序的。
使用 Bucket.Cursor()對其進行迭代

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
})

Cursor 有 5 種方法進行迭代

  1. First() Move to the first key. 
  2. Last() Move to the last key.
  3. Seek() Move to a specific key.
  4. Next() Move to the next key.
  5. Prev() Move to the previous key.

每一個方法都返回 (key []byte, value []byte) 兩個值
當方法所指值不存在時返回 兩個 nil 值,發生在以下情況:

  1. 迭代到最後一個鍵值對時,再一次調用 Cursor.Next() 
  2. 當前所指爲第一個鍵值對時,調用 Cursor.Prev()
  3. 當使用 4.Next() 和 5. Prev()方法而未使用 1.First() 2.Last() 3. Seek()指定初始位置時

⚠️特殊情況:當 key 爲 非 nilvaluenil 是,說明這是嵌套桶,value 值是子桶,使用 Bucket.Bucket() 方法訪問 子桶,參數是 key

db.View(func(tx *bolt.Tx) error {
    c := b.Cursor()
    fmt.Println(c.First())
    k, v := c.Prev()
    fmt.Println(k == nil, v == nil) // true,true

    if k != nil && v == nil {
        subBucket := b.Bucket()
        // doanything
    }
    return nil
})

前綴遍歷

通過使用 Cursor我們能夠做到一些特殊的遍歷,如:遍歷擁有特定前綴的 鍵值對

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", k, v)
    }

    return nil
})

範圍遍歷

在一個範圍裏遍歷,如:使用可排序的時間編碼(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
})

⚠️:Golang 實現的 RFC3339Nano 是不可排序的

ForEach

在桶中有值的情況下,可以使用 ForEach()遍歷

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
})

⚠️:在 ForEach()中遍歷的鍵值對需要copy()到事務外才能在事務外使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章