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()到事务外才能在事务外使用