go第三方库
bolt
简介
用 go 实现的键值对数据库,类似与 go-sqlite3 (cgo实现的)。
它支持完整的序列化事务,ACID和无锁的多版本控制(多个读,一个写)。它的定位主要是小型数据存储,支持跨平台。
类型
DB,bucket(桶),Tx 和 Cursor(光标)。db是表示一个磁盘上的文件,它bucket的集合。bucket是键值对的集合。
Transactions提供read-only(只读)或(read-write)读写访问数据库,只读 transactions 可以获取键值对和使用cursor(游标)迭代遍历数据集。读写事务可以删除,创建 bucket,插入,删除键值对。在同一时刻只能有一个读写事务被使用。
使用
打开数据库
func main(){
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
...
}
为了防止出现同时打开或同时操作同一个数据库的导致以外的情况出现
db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
事务
// 读写事务
err := db.Update(func(tx *bolt.Tx) error {
...
// 通过返回 nil 提交事务
// 在任意地方返回错误进行事务的回滚。
// update 方法对事务操作进行了包装,防止出现疏忽导致意外。
// 在读写事务中允许一切数据库操作
return nil
})
// 只读事务
err := db.View(func(tx *bolt.Tx) error {
...
return nil
})
// 批量事务
err := db.Batch(func(tx *bolt.Tx) error {
...
return nil
})
// 手动管理事务
// Start a writable transaction.
tx, err := db.Begin(true)
if err != nil {
return err
}
defer tx.Rollback()
// Use the transaction...
_, err := tx.CreateBucket([]byte("MyBucket"))
if err != nil {
return err
}
// Commit the transaction and check for error.
if err := tx.Commit(); err != nil {
return err
}
关于事务的一些注意事项
bucket
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("MyBucket"))
if err != nil {
return fmt.Errorf("create bucket: %s", err)
}
return nil
})
使用 Tx.DeleteBucket()
删除。
使用键值对
// 设置
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
err := b.Put([]byte("answer"), []byte("42"))
return err
})
// 获取
db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
v := b.Get([]byte("answer")) // 不会返回错误,除非出现了系统错误。
fmt.Printf("The answer is: %s\n", v)
return nil
})
自增长唯一标识
// 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
...
}
遍历键
// 常用遍历方法
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
})
// 前缀扫描
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
})
// 范围扫描
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
})
// 完整遍历
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
})
桶里面可以嵌套子桶,如果键不为空,值为空的情况就是存在子桶,子桶的创建方法和 db 创建桶的 api 方法相同。