buntdb

buntdb

Go语言实现的高性能内存键值存储

BuntDB是一个Go语言实现的内存键值存储库。它具备数据持久化、ACID事务、并发控制等特性,支持自定义索引和地理空间数据。BuntDB适用于对速度要求高的项目,提供简单API、多种索引类型和数据过期等功能。作为嵌入式数据库,BuntDB为Go应用提供高性能的数据存储选择。

BuntDB数据库键值存储索引事务Github开源项目
<p align="center"> <img src="logo.png" width="307" height="150" border="0" alt="BuntDB"> <br> <a href="https://godoc.org/github.com/tidwall/buntdb"><img src="https://yellow-cdn.veclightyear.com/835a84d5/bc96bfb2-f4a2-4ba2-a800-34595e2abed3.svg?style=flat-square" alt="Godoc"></a> <a href="https://github.com/tidwall/buntdb/blob/master/LICENSE"><img src="https://yellow-cdn.veclightyear.com/835a84d5/689c2473-5663-405c-a21a-e0229711e12d.svg?style=flat-square" alt="LICENSE"></a> </p>

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 })

还有AscendGreaterOrEqualAscendLessThanAscendRangeAscendEqualDescendDescendLessOrEqualDescendGreaterThanDescendRangeDescendEqual。请查看文档以获取有关这些函数的更多信息。

自定义索引

最初,所有数据都存储在一个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,还有IndexIntIndexUintIndexFloat。 这些是用于索引的内置类型。你可以选择使用这些或创建自己的类型。

所以要创建一个按年龄键数字排序的索引,我们可以使用:

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 textGeoJSON

要向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 })

这将获取所有三个位置。

k-最近邻

使用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 索引

可以在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")), )

这将创建一个多值索引,其中姓氏是升序,年龄是降序。

排序规则i18n索引

使用外部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()Delete()等操作引起的数据库更改的日志。

该文件的格式如下:

set key:1 value1
set key:2 value2
set key:1 value3
del key:2
...

当数据库再次打开时,它将回读aof文件并按确切顺序处理每个命令。 这个读取过程在数据库打开时只发生一次。 从那时起,该文件只会被追加。 正如你可能猜到的,这个日志文件会随着时间的推移变得很大。 有一个后台程序会在日志文件变得太大时自动缩小它。 还有一个Shrink()函数,它会重写aof文件,使其只包含数据库中的项目。 缩小操作不会锁定数据库,因此在缩小过程中可以继续进行读写事务。

持久性和fsync

默认情况下,BuntDB每秒钟在aof文件上执行一次fsync。这意味着可能会丢失最多一秒钟的数据。如果你需要更高的持久性,可以将可选的数据库配置设置Config.SyncPolicy设为Always

Config.SyncPolicy有以下选项:

  • Never - fsync由操作系统管理,较不安全
  • EverySecond - 每秒fsync一次,快速且更安全,这是默认设置
  • Always - 每次写入后fsync,非常持久,但较慢

配置

以下是一些可用于更改数据库各种行为的配置选项。

  • SyncPolicy 调整数据同步到磁盘的频率。该值可以是Never、EverySecond或Always。默认为EverySecond。
  • AutoShrinkPercentage 被后台进程用来在aof文件大小大于上次缩小后文件大小的百分比时触发aof文件的缩小。例如,如果此值为100,上次缩小过程生成了100MB的文件,那么新的aof文件必须达到200MB才会触发缩小。默认为100。
  • AutoShrinkMinSize 定义了在自动缩小可以发生之前aof文件的最小大小。默认为32MB。
  • AutoShrinkDisabled 关闭自动后台缩小。默认为false。

要更新配置,你应该调用ReadConfig,然后调用SetConfig。例如:

var config buntdb.Config if err := db.ReadConfig(&config); err != nil { log.Fatal(err) } if err := db.SetConfig(config); err != nil { log.Fatal(err) }

性能

BuntDB有多快?

这里是在Raft Store实现中使用BuntDB的一些基准测试示例。

你也可以从项目根目录运行标准Go基准测试工具:

go test --bench=.

BuntDB-基准测试

有一个专门的工具是为BuntDB基准测试而创建的。

以下是在MacBook Pro 15" 2.8 GHz Intel Core i7上运行基准测试的结果:

$ buntdb-benchmark -q
GET: 4609604.74 operations per second
SET: 248500.33 operations per second
ASCEND_100: 2268998.79 operations per second
ASCEND_200: 1178388.14 operations per second
ASCEND_400: 679134.20 operations per second
ASCEND_800: 348445.55 operations per second
DESCEND_100: 2313821.69 operations per second
DESCEND_200: 1292738.38 operations per second
DESCEND_400: 675258.76 operations per second
DESCEND_800: 337481.67 operations per second
SPATIAL_SET: 134824.60 operations per second
SPATIAL_INTERSECTS_100: 939491.47 operations per second
SPATIAL_INTERSECTS_200: 561590.40 operations per second
SPATIAL_INTERSECTS_400: 306951.15 operations per second
SPATIAL_INTERSECTS_800: 159673.91 operations per second

要安装这个工具:

go get github.com/tidwall/buntdb-benchmark

联系方式

Josh Baker @tidwall

许可证

BuntDB源代码可在MIT 许可证下使用。

编辑推荐精选

Keevx

Keevx

AI数字人视频创作平台

Keevx 一款开箱即用的AI数字人视频创作平台,广泛适用于电商广告、企业培训与社媒宣传,让全球企业与个人创作者无需拍摄剪辑,就能快速生成多语言、高质量的专业视频。

即梦AI

即梦AI

一站式AI创作平台

提供 AI 驱动的图片、视频生成及数字人等功能,助力创意创作

扣子-AI办公

扣子-AI办公

AI办公助手,复杂任务高效处理

AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!

TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
蛙蛙写作

蛙蛙写作

AI小说写作助手,一站式润色、改写、扩写

蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。

AI辅助写作AI工具蛙蛙写作AI写作工具学术助手办公助手营销助手AI助手
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

热门AI助手AI对话AI工具聊天机器人
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

AI办公办公工具AI工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图热门
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

热门AI开发模型训练AI工具讯飞星火大模型智能问答内容创作多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

下拉加载更多