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 种方法进行迭代
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)
两个值
当方法所指值不存在时返回 两个 nil
值,发生在以下情况:
- 迭代到最后一个键值对时,再一次调用
Cursor.Next()
- 当前所指为第一个键值对时,调用
Cursor.Prev()
- 当使用 4.
Next()
和 5.Prev()
方法而未使用 1.First()
2.Last()
3.Seek()
指定初始位置时
⚠️特殊情况:当 key
为 非 nil
但 value
是 nil
是,说明这是嵌套桶,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()
到事务外才能在事务外使用