badger.open()必须显式指定dir和valuedir,否则静默创建临时目录致数据丢失;路径需绝对且可写,写操作须事务commit,读取值必须valuecopy,value.log需定时gc。

badger.Open() 传参必须显式指定 Dir 和 ValueDir
不设这两个路径,Badger 就会静默创建临时目录,进程一退出数据全丢;只设 Dir 不设 ValueDir,新版(v3)会默认用 Dir 的父目录当 ValueDir,跨平台行为不一致,极大概率在 CI 或 Docker 里挂掉。
实操建议:
- 用
filepath.Abs("./data")转成绝对路径,别用"./data" - 两个路径都显式赋值:
opt.Dir = dataDir; opt.ValueDir = dataDir(除非你真要 SSD/HDD 分离) - 启动前用
os.MkdirAll(dataDir, 0755)和os.MkdirAll(valueDir, 0755)确保目录存在且可写 - 别依赖
badger.DefaultOptions(""),它在 v3 中已不推荐,行为模糊
写入必须走事务,且 txn.Commit() 不能漏
Badger 没有 db.Put() 这种直写接口,所有写操作都得包在写事务里。漏掉 txn.Commit(),程序不报错、日志没异常、HTTP 返回 200,但重启后数据就没了——这是生产环境最隐蔽的数据丢失来源。
实操建议:
- 永远用
defer txn.Discard()开头,再在成功路径上用txn.Commit()替换掉它 - 批量写优先用
db.Update(),它自动处理 commit/discard,比手写db.NewTransaction(true)安全得多 - 别在事务里做耗时操作:HTTP 请求、JSON 解析、循环计算全得挪到事务外
- 如果要用
txn.SetEntry()批量塞数据,确保Entry.Key和Entry.Value都是[]byte,别传 struct 或 int
读取值必须调用 ValueCopy,不能直接用 Value()
item.Value() 返回的是底层内存切片,生命周期绑定事务。事务一结束(比如 txn.Commit() 或函数返回),这块内存可能被 LSM 后台线程覆写,后续读就是脏数据或 panic。
实操建议:
- 安全写法:
val, err := item.ValueCopy(nil),nil表示让 Badger 自动分配内存 - 想复用底层数组?传一个足够长的
[]byte,但注意cap必须 >= 实际 value 长度,否则会重新分配 - 结构体反序列化必须基于
ValueCopy结果:json.Unmarshal(val, &user),别用item.Value()直接解 - 迭代器遍历中,每次
iter.Next()后都要重新调item.ValueCopy(),不能缓存引用
value.log 不会自动回收,磁盘吃满前必须手动 GC
Badger 把 value 单独存进 value.log 文件,删 key 只是标记 value 为垃圾,不会立即释放磁盘。不干预的话,value.log 只增不减,直到 df -h 报 98%,服务开始卡顿甚至 OOM。
实操建议:
- 上线后第一件事:加定时任务调
db.RunValueLogGC(0.7)(70% 无效时触发),安排在凌晨低峰期 - 调小
Options.ValueLogFileSize(默认 1GB),设为256 * 1024 * 1024可加快单次 GC 效率 - 检查是否误设了
WithValueThreshold(0):这会让所有 value 进 log,应设为32或64 - 大 value(>1KB)别硬塞:存本地文件或对象存储,Badger 里只存路径或 SHA256
ValueLog GC 是阻塞操作,且无法中断;一旦开始,写入会停顿几秒到几十秒,取决于 value.log 大小和磁盘 I/O。这个时间点必须可控,不能让它在高峰期触发。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











