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