BuntDB是一个用纯Go语言编写的低级内存键值存储。它能持久化到磁盘,符合ACID原则,并使用锁机制支持多个读取器和单个写入器。它支持自定义索引和地理空间数据。对于需要可靠数据库并且偏好速度而非数据大小的项目来说,它是理想的选择。
要开始使用BuntDB,请安装Go并运行go get
:
$ go get -u github.com/tidwall/buntdb
这将获取该库。
BuntDB的主要对象是DB
。要打开或创建数据库,使用buntdb.Open()
函数:
package main import ( "log" "github.com/tidwall/buntdb" ) func main() { // 打开data.db文件。如果不存在则创建。 db, err := buntdb.Open("data.db") if err != nil { log.Fatal(err) } defer db.Close() ... }
也可以通过使用:memory:
作为文件路径来打开一个不会持久化到磁盘的数据库。
buntdb.Open(":memory:") // 打开一个不会持久化到磁盘的文件。
所有读写操作都必须在事务内进行。BuntDB一次只能打开一个写事务,但可以同时有多个并发的读事务。每个事务都维护一个稳定的数据库视图。换句话说,一旦事务开始,该事务的数据就不能被其他事务更改。
事务在一个暴露Tx
对象的函数中运行,该对象代表事务状态。在事务内部,所有数据库操作都应该使用这个对象执行。在事务内部时,你不应该访问原始的DB
对象。这样做可能会产生副作用,比如阻塞你的应用程序。
当事务失败时,它会回滚,并撤销在该事务期间对数据库所做的所有更改。有一个单一的返回值可用于关闭事务。对于读写事务,以这种方式返回错误将强制事务回滚。当读写事务成功时,所有更改都会持久化到磁盘。
当你不需要对数据进行更改时,应该使用只读事务。只读事务的优点是可以有多个并发运行。
err := db.View(func(tx *buntdb.Tx) error { ... return nil })
当你需要对数据进行更改时,应该使用读写事务。一次只能有一个读写事务运行。所以确保在完成后尽快关闭它。
err := db.Update(func(tx *buntdb.Tx) error { ... return nil })
要设置一个值,你必须打开一个读写事务:
err := db.Update(func(tx *buntdb.Tx) error { _, _, err := tx.Set("mykey", "myvalue", nil) return err })
要获取值:
err := db.View(func(tx *buntdb.Tx) error { val, err := tx.Get("mykey") if err != nil{ return err } fmt.Printf("值为 %s\n", val) return nil })
获取不存在的值将导致ErrNotFound
错误。
数据库中的所有键值对都按键排序。要迭代键:
err := db.View(func(tx *buntdb.Tx) error { err := tx.Ascend("", func(key, value string) bool { fmt.Printf("键: %s, 值: %s\n", key, value) return true // 继续迭代 }) return err })
还有AscendGreaterOrEqual
、AscendLessThan
、AscendRange
、AscendEqual
、Descend
、DescendLessOrEqual
、DescendGreaterThan
、DescendRange
和DescendEqual
。请查看文档以获取有关这些函数的更多信息。
最初,所有数据都存储在一个B树中,每个项目有一个键和一个值。所有这些项目都按键排序。这对于快速从键获取值或迭代键很有用。欢迎浏览B树实现。
你还可以创建自定义索引,允许对值进行排序和迭代。自定义索引也使用B树,但它更灵活,因为它允许自定义排序。
例如,假设你想创建一个用于排序名字的索引:
db.CreateIndex("names", "*", buntdb.IndexString)
这将创建一个名为names
的索引,用于存储和排序所有值。第二个参数是用于过滤键的模式。*
通配符参数表示我们想接受所有键。IndexString
是一个内置函数,对值执行不区分大小写的排序。
现在你可以添加各种名字:
db.Update(func(tx *buntdb.Tx) error { tx.Set("user:0:name", "tom", nil) tx.Set("user:1:name", "Randi", nil) tx.Set("user:2:name", "jane", nil) tx.Set("user:4:name", "Janet", nil) tx.Set("user:5:name", "Paula", nil) tx.Set("user:6:name", "peter", nil) tx.Set("user:7:name", "Terri", nil) return nil })
最后你可以迭代索引:
db.View(func(tx *buntdb.Tx) error { tx.Ascend("names", func(key, val string) bool { fmt.Printf(buf, "%s %s\n", key, val) return true }) return nil })
输出应该是:
user:2:name jane
user:4:name Janet
user:5:name Paula
user:6:name peter
user:1:name Randi
user:7:name Terri
user:0:name tom
模式参数可以用来过滤 键,像这样:
db.CreateIndex("names", "user:*", buntdb.IndexString)
现在只有键前缀为user:
的项目会被添加到names
索引中。
除了IndexString
,还有IndexInt
、IndexUint
和IndexFloat
。
这些是用于索引的内置类型。你可以选择使用这些或创建自己的类型。
所以要创建一个按年龄键数字排序的 索引,我们可以使用:
db.CreateIndex("ages", "user:*:age", buntdb.IndexInt)
然后添加值:
db.Update(func(tx *buntdb.Tx) error { tx.Set("user:0:age", "35", nil) tx.Set("user:1:age", "49", nil) tx.Set("user:2:age", "13", nil) tx.Set("user:4:age", "63", nil) tx.Set("user:5:age", "8", nil) tx.Set("user:6:age", "3", nil) tx.Set("user:7:age", "16", nil) return nil })
db.View(func(tx *buntdb.Tx) error { tx.Ascend("ages", func(key, val string) bool { fmt.Printf(buf, "%s %s\n", key, val) return true }) return nil })
输出应该是:
user:6:age 3
user:5:age 8
user:2:age 13
user:7:age 16
user:0:age 35
user:1:age 49
user:4:age 63
BuntDB通过在R树中存储矩形来支持空间索引。R树的组织方式类似于B树,两者都是平衡树。但是,R树特别之处在于它可以操作多维数据。这对于地理空间应用非常有用。
要创建空间索引,请使用CreateSpatialIndex
函数:
db.CreateSpatialIndex("fleet", "fleet:*:pos", buntdb.IndexRect)
然后IndexRect
是一个内置函数,它将矩形字符串转换为R树可以使用的格式。这个函数开箱即用很 容易使用,但你可能会发现创建一个从不同格式渲染的自定义函数更好,比如Well-known text或GeoJSON。
要向fleet
索引添加一些经纬度点:
db.Update(func(tx *buntdb.Tx) error { tx.Set("fleet:0:pos", "[-115.567 33.532]", nil) tx.Set("fleet:1:pos", "[-116.671 35.735]", nil) tx.Set("fleet:2:pos", "[-113.902 31.234]", nil) return nil })
然后你可以在索引上运行Intersects
函数:
db.View(func(tx *buntdb.Tx) error { tx.Intersects("fleet", "[-117 30],[-112 36]", func(key, val string) bool { ... return true }) return nil })
这将获取所有三个位置。
使用Nearby
函数按照从近到远的顺序获取所有位置:
db.View(func(tx *buntdb.Tx) error { tx.Nearby("fleet", "[-113 33]", func(key, val string, dist float64) bool { ... return true }) return nil })
括号语法[-117 30],[-112 36]
是BuntDB独有的,用于处理内置的矩形。但你不局限于这种语法。在CreateSpatialIndex
期间你选择使用的任何Rect函数都将用于处理参数,在这种情况下是IndexRect
。
2D矩形: [10 15],[20 25]
最小XY: "10x15", 最大XY: "20x25"
3D矩形: [10 15 12],[20 25 18]
最小XYZ: "10x15x12", 最大XYZ: "20x25x18"
2D点: [10 15]
XY: "10x15"
经纬度点: [-112.2693 33.5123]
纬度经度: "33.5123 -112.2693"
经纬度边界框: [-112.26 33.51],[-112.18 33.67]
最小纬度经度: "33.51 -112.26", 最大纬度经度: "33.67 -112.18"
注意: 经度是Y轴且在左侧,纬度是X轴且在右侧。
你也可以用-inf
和+inf
表示无穷大
。
例如,你可能有以下点([X Y M]
其中XY是一个点,M是时间戳):
[3 9 1]
[3 8 2]
[4 8 3]
[4 7 4]
[5 7 5]
[5 6 6]
然后你可以通过调用Intersects
来搜索所有M在2-4之间的点。
tx.Intersects("points", "[-inf -inf 2],[+inf +inf 4]", func(key, val string) bool { println(val) return true })
这将返回:
[3 8 2]
[4 8 3]
[4 7 4]
可以在JSON文档中的单个字段上创建索引。BuntDB在底层使用GJSON。
例如:
package main import ( "fmt" "github.com/tidwall/buntdb" ) func main() { db, _ := buntdb.Open(":memory:") db.CreateIndex("last_name", "*", buntdb.IndexJSON("name.last")) db.CreateIndex("age", "*", buntdb.IndexJSON("age")) db.Update(func(tx *buntdb.Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) return nil }) db.View(func(tx *buntdb.Tx) error { fmt.Println("按姓氏排序") tx.Ascend("last_name", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) fmt.Println("按年龄排序") tx.Ascend("age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) fmt.Println("按30-50岁年龄范围排序") tx.AscendRange("age", `{"age":30}`, `{"age":50}`, func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) }
结果:
按姓氏排序
3: {"name":{"first":"Carol","last":"Anderson"},"age":52}
4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
按年龄排序
4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
3: {"name":{"first":"Carol","last":"Anderson"},"age":52}
按30-50岁年龄范围排序
1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
BuntDB可以在单个索引上连接多个值。 这类似于传统SQL数据库中的多列索引。
在这个例子中,我们在"name.last"和"age"上创建一个多值索引:
db, _ := buntdb.Open(":memory:") db.CreateIndex("last_name_age", "*", buntdb.IndexJSON("name.last"), buntdb.IndexJSON("age")) db.Update(func(tx *buntdb.Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil) tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil) return nil }) db.View(func(tx *buntdb.Tx) error { tx.Ascend("last_name_age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // 输出: // 5: {"name":{"first":"Sam","last":"Anderson"},"age":51} // 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} // 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} // 6: {"name":{"first":"Melinda","last":"Prichard"},"age":44} // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
任何索引都可以通过用buntdb.Desc
包装其less函数来降序排列。
db.CreateIndex("last_name_age", "*", buntdb.IndexJSON("name.last"), buntdb.Desc(buntdb.IndexJSON("age")), )
这将创建一个多值索引,其中姓氏是升序,年龄是降序。
使用外部collate包,可以创建按指定语言排序的索引。这类似于传统数据库中的SQL COLLATE关键字。
安装:
go get -u github.com/tidwall/collate
例如:
import "github.com/tidwall/collate" // 用法语不区分大小写排序 db.CreateIndex("name", "*", collate.IndexString("FRENCH_CI")) // 指定数字应该按数值排 序("2" < "12") // 并使用逗号表示小数点 db.CreateIndex("amount", "*", collate.IndexString("FRENCH_NUM"))
JSON索引也支持排序规则:
db.CreateIndex("last_name", "*", collate.IndexJSON("CHINESE_CI", "name.last"))
查看collate项目以获取更多信息。
可以通过在Set
函数中使用SetOptions
对象来设置TTL
来自动驱逐项目。
db.Update(func(tx *buntdb.Tx) error { tx.Set("mykey", "myval", &buntdb.SetOptions{Expires:true, TTL:time.Second}) return nil })
现在mykey
将在一秒钟后自动删除。你可以通过使用相同的键/值再次设置值来移除TTL,但options参数设置为nil。
BuntDB目前不支持在迭代过程中删除键。 作为解决方法,你需要在迭代器完成后删除键。
var delkeys []string tx.AscendKeys("object:*", func(k, v string) bool { if someCondition(k) == true { delkeys = append(delkeys, k) } return true // 继续 }) for _, k := range delkeys { if _, err = tx.Delete(k); err != nil { return err } }
BuntDB使用AOF(仅追加文件),这是一个记录所有由Set()