<img src="https://user-images.githubusercontent.com/6065007/141310364-62d7eebb-2cbb-4949-80ed-5cd20f705405.png">
English | 简体中文
NutsDB 是一个用纯 Go 编写的简单、快速、可嵌入且持久的键/值存储。
它支持完全可序列化的事务以及 List、Set、SortedSet 等多种数据结构。 所有操作都发生在 Tx 内部。 Tx 代表一个事务,可以是只读的,也可以是读写的。 只读事务可以读取给定存储桶和给定键的值或迭代一组键值对。 读写事务可以从数据库中读取、更新和删除键。
我们可以在NutsDB的文档网站了解更多:NutsDB Documents
欢迎对NutsDB感兴趣的加群、一起开发,具体看这个issue:https://github.com/nutsdb/nutsdb/issues/116 。

📢 注意:从v0.9.0开始,DefaultOptions 里面的 defaultSegmentSize 做了调整从原来的 8MB 变成了 256MB,如果你原来设置 256MB 不用改,如果原来使用的是默认值的,需要手动改成 8MB,不然原来的数据不会解析。这边的大小调整原因是从 v0.9.0 开始有对文件描述符的缓存(详解见PR https://github.com/nutsdb/nutsdb/issues/164 ),所以需要用户看下自己的文件描述符数量,有不清楚可以提issue或者群里问。
nutsdb v1.0.0 之后,由于底层数据存储协议的变化,旧版本的数据不兼容。 请在使用新版本之前重写数据。 并且当前的 Bucket 需要手动创建。 详细请参见Bucket使用文档。
https://www.bilibili.com/video/BV1T34y1x7AS/

NutsDB的安装很简单,首先保证 Golang 已经安装好 (版本要求1.11以上). 然后在终端执行命令:
go get -u github.com/nutsdb/nutsdb
要打开数据库需要使用nutsdb.Open()这个方法。其中用到的选项(options)包括 Dir , EntryIdxMode和 SegmentSize,在调用的时候这些参数必须设置。官方提供了DefaultOptions的选项,直接使用nutsdb.DefaultOptions即可。当然你也可以根据需要自己定义。
例子:
package main
import (
"log"
"github.com/nutsdb/nutsdb"
)
func main() {
db, err := nutsdb.Open(
nutsdb.DefaultOptions,
nutsdb.WithDir("/tmp/nutsdb"), // 数据库会自动创建这个目录文件
)
if err != nil {
log.Fatal(err)
}
defer db.Close()
...
}
Dir 代表数据库存放数据的目录
EntryIdxMode 代表索引entry的模式.
EntryIdxMode 包括选项: HintKeyValAndRAMIdxMode 、 HintKeyAndRAMIdxMode和 HintBPTSparseIdxMode。
其中HintKeyValAndRAMIdxMode 代表纯内存索引模式(key和value都会被cache)。
HintKeyAndRAMIdxMode 代表内存+磁盘的索引模式(只有key被cache)。
HintBPTSparseIdxMode(v0.4.0之后的版本支持) 是专门节约内存的设计方案,单机10亿条数据,只要80几M内存。但是读性能不高,需要自己加缓存来加速。
RWMode 代表读写模式. RWMode 包括两种选项: FileIO and MMap.
FileIO 用标准的 I/O读写。 MMap 代表使用mmap进行读写。
SegmentSize 代表数据库的数据单元,每个数据单元(文件)为SegmentSize,现在默认是8。注意:从大于0.8.0版本开始,默认SegmentSize变成256MB
MB,这个可以自己配置。但是一旦被设置,下次启动数据库也要用这个配置,不然会报错。详情见 限制和警告。
NodeNum 代表节点的号码.默认 NodeNum是 1. NodeNum 取值范围 [1,1023] 。
SyncEnable 代表调用了 Sync() 方法.
如果 SyncEnable 为 false, 写性能会很高,但是如果遇到断电或者系统奔溃,会有数据丢失的风险。
如果 SyncEnable 为 true,写性能会相比false的情况慢很多,但是数据更有保障,每次事务提交成功都会落盘。
StartFileLoadingMode 代表启动数据库的载入文件的方式。参数选项同RWMode。
GCWhenClose 表示调用 db.Close() 时主动 GC。Nutsdb 预设不会立即在 db.Close() 时触发 GC.
CommitBufferSize 表示为事务预分配的内存大小。Nutsdb 将预分配内存以减少内存分配的次数。
ErrorHandler 处理事务执行期间发生的错误。
LessFunc 表示对 key 进行排序的函数。Nutsdb 默认按字典序对 key 进行排序。
MergeInterval 表示自动化 Merge 的间隔,0 表示不触发自动化 Merge,默认间隔为 2 小时。
MaxBatchCount 表示批量写入的最大条数。
MaxBatchSize 表示批量写入的最大字节数。
HintKeyAndRAMIdxCacheSize 表示 HintKeyAndRAMIdxMode 模式下的缓存大小。设置为 0 时禁用缓存。
TTLConfig 表示 TTL 扫描和批处理行为的配置(默认值:BatchSize=100, BatchTimeout=1s, QueueSize=1000)。
EnableHintFile 表示是否启用提示文件功能。
EnableMergeV2 表示是否启用 Merge V2 算法。
ListImpl 指定 List 数据结构的实现类型(默认:ListImplBTree)。
EnableWatch 表示是否启用监视功能。如果 EnableWatch 为 true,将启用监视功能。监视功能默认禁用。
Clock 为 TTL 计算提供时间操作。如果为 nil,默认使用 RealClock。
推荐使用默认选项的方式。兼顾了持久化+快速的启动数据库。当然具体还要看你场景的要求。
以下配置是比较保守的方式。 如果你对写性能要求比较高,可以设置SyncEnable等于false,RWMode改成MMap,写性能会得到极大提升,缺点是可能会丢数据(例如遇到断电或者系统奔溃)
var DefaultOptions = func() Options {
return Options{
EntryIdxMode: HintKeyValAndRAMIdxMode,
SegmentSize: defaultSegmentSize,
NodeNum: 1,
RWMode: FileIO,
SyncEnable: true,
CommitBufferSize: 4 * MB,
MergeInterval: 2 * time.Hour,
MaxBatchSize: (15 * defaultSegmentSize / 4) / 100,
MaxBatchCount: (15 * defaultSegmentSize / 4) / 100 / 100,
HintKeyAndRAMIdxCacheSize: 0,
TTLConfig: ttl.DefaultConfig(),
EnableHintFile: false,
EnableMergeV2: false,
ListImpl: ListImplementationType(ListImplBTree),
EnableWatch: false,
Clock: ttl.NewRealClock(),
}
}()
NutsDB为了保证隔离性,防止并发读写事务时候数据的不一致性,同一时间只能执行一个读写事务,但是允许同一时间执行多个只读事务。 从v0.3.0版本开始,NutsDB遵循标准的ACID原则。(参见限制和警告)
err := db.Update(
func(tx *nutsdb.Tx) error {
...
return nil
})
err := db.View(
func(tx *nutsdb.Tx) error {
...
return nil
})
从上面的例子看到 DB.View() 和DB.Update() 这两个是数据库调用事务的主要方法。他们本质上是基于 DB.Begin()方法进行的包装。他们可以帮你自动管理事务的生命周期,从事务的开始、事务的执行、事务提交或者回滚一直到事务的安全的关闭为止,如果中间有错误会返回。所以一般情况下推荐用这种方式去调用事务。
这好比开车有手动挡和自动挡一样, DB.View() 和DB.Update()等于提供了自动档的效果。
如果你需要手动去开启、执行、关闭事务,你会用到DB.Begin()方法开启一个事务,tx.Commit() 方法用来提交事务、tx.Rollback()方法用来回滚事务
例子:
//开始事务
tx, err := db.Begin(true)
if err != nil {
return err
}
bucket := "bucket1"
key := []byte("foo")
val := []byte("bar")
// 使用事务
if err = tx.Put(bucket, key, val, nutsdb.Persistent); err != nil {
// 回滚事务
tx.Rollback()
} else {
// 提交事务
if err = tx.Commit(); err != nil {
tx.Rollback()
return err
}
}
buckets中文翻译过来是桶的意思,你可以理解成类似mysql的table表的概念,也可以理解成命名空间,或者多租户的概念。 所以你可以用他存不同的key的键值对,也可以存相同的key的键值对。所有的key在一个bucket里面不能重复。
例子:
key := []byte("key001")
val := []byte("val001")
bucket001 := "bucket001"
if err := db.Update(
func(tx *nutsdb.Tx) error {
if err := tx.Put(bucket001, key, val, 0); err != nil {
return err
}
return nil
}); err != nil {
log.Fatal(err)
}
bucket002 := "bucket002"
if err := db.Update(
func(tx *nutsdb.Tx) error {
if err := tx.Put(bucket002, key, val, 0); err != nil {
return err
}
return nil
}); err != nil {
log.Fatal(err)
}
这边注意下,这个bucket和你使用数据结构有关,不同数据索引结构,用同一个bucket,也是不同的。比如你定义了一个bucket,命名为bucket_foo,比如你要用list这个数据结构,使用 tx.RPush加数据,必须对应他的数据结构去从这个bucket_foo查询或者取出,比如用 tx.RPop,tx.LRange 等,不能用tx.Get(和GetAll、Put、Delete、RangeScan等同一索引类型)去读取这个bucket_foo里面的数据,因为索引结构不同。其他数据结构如Set、Sorted Set同理。
下面说明下迭代buckets 和 删除bucket。它们都用到了ds。
ds表示数据结构,支持如下: * DataStructureSet * DataStructureSortedSet * DataStructureBPTree * DataStructureList
目前支持的EntryIdxMode如下:
IterateBuckets支持迭代指定ds的迭代。
if err := db.View(
func(tx *nutsdb.Tx) error {
return tx.IterateBuckets(nutsdb.DataStructureBPTree, "*", func(bucket string) bool {
fmt.Println("bucket: ", bucket)
// true: continue, false: break
return true
})
}); err != nil {
log.Fatal(err)
}
DeleteBucket支持删除指定的bucket,需要两个参数ds和bucket。
if err := db.Update(
func(tx *nutsdb.Tx) error {
return tx.DeleteBucket(nutsdb.DataStructureBPTree, bucket)
}); err != nil {
log.Fatal(err)
}
将key-value键值对保存在一个bucket, 你可以使用 tx.Put 这个方法:
if err := db.Update(
func(tx *nutsdb.Tx) error {
key := []byte("name1")
val := []byte("val1")
bucket := "bucket1"
if err := tx.Put(bucket, key, val, 0); err != nil {
return err
}
return nil
}); err != nil {
log.Fatal(err)
}
上面的代码执行之后key为"name1"和value值"val1"被保存在命名为bucket1的bucket里面。
如果你要做更新操作,你可以仍然用tx.Put方法去执行,比如下面的例子把value的值改成"val1-modify":
```golang if err := db.Update( func(tx *nutsdb.Tx) error { key := []byte("name1") val := []byte("val1-modify") // 更新值 bucket := "bucket1" if err := tx.Put(bucket, key, val, 0); err != ni
$ claude mcp add nutsdb \
-- python -m otcore.mcp_server <graph>