badger 使用需显式指定 dir 和 valuedir 路径、写入必须用 db.update() 或正确提交事务、读取必须调用 item.valuecopy(nil)、关闭前必须且仅调用一次 db.close()。

badger.Open() 必须显式指定 Dir 和 ValueDir
不设这两个路径,Badger 会静默创建临时目录,进程退出后数据全丢——你写进去了,但重启就没了,还查不到任何报错。
常见错误是只传一个路径:badger.Open(badger.DefaultOptions("/data")),这会让 ValueDir 落在系统临时目录(如 /tmp),和 Dir 不一致,导致后续 GC 失败或 manifest 加载异常。
- 正确做法:显式赋值,哪怕两者相同:
opts := badger.DefaultOptions("/data")<br>opts.ValueDir = "/data" - 路径必须是可写目录:用
os.MkdirAll("/data", 0755)提前创建,Badger 不会自动建父级目录 - 避免相对路径:
"./data"在不同工作目录下行为不一致,优先用filepath.Abs("./data")转成绝对路径
写入必须用 db.Update() 或显式写事务
db.Update() 是最安全的写入口,它自动处理 Commit() 或 Discard();手写事务容易漏掉 txn.Commit(),结果是“没报错、没panic、但数据根本没存进去”。
错误示例:txn := db.NewTransaction(true); txn.Set(k,v); // 忘记 Commit() —— 程序跑完也查不到 key。
- 高频单条写:直接用
db.Update(func(txn *badger.Txn) error { return txn.Set(k, v) }) - 批量写:优先用
WriteBatch,避免手动管理事务生命周期:wb := db.NewWriteBatch(); wb.Set(k,v,0); wb.Flush() - 千万别在
db.View()里调db.Update(),会死锁并 panic “recursive txn”
读取值必须调用 Item.ValueCopy(nil)
Item.Value() 返回的是事务内共享内存切片,事务一结束(比如 db.View() 函数返回后),这块内存可能已被复用或覆盖。直接用 string(item.Value()) 或存到 map 里,大概率读到空、乱码或 panic。
典型现象:循环遍历 iterator 时,所有 key 对应的 value 都一样——因为每次都覆盖了上一次的底层缓冲。
- 安全写法:
val, err := item.ValueCopy(nil),nil表示让 Badger 自动分配内存 - 想复用底层数组?传一个足够长的
[]byte,但得自己确保容量够,否则ValueCopy会重新分配 - 结构体反序列化前,务必先
ValueCopy,再json.Unmarshal(val, &s)
关闭数据库前必须调 db.Close(),且不能重复调
不调 db.Close() 就退出进程,value log 文件会损坏,下次启动直接报 "manifest has unsupported version" 或 "checksum mismatch",不是配置问题,是文件已坏。
重复调 Close() 会 panic,所以别在 defer 里套 defer,也别在 signal handler 和 main 结束处都调。
- 标准写法:
defer db.Close()放在badger.Open()成功之后 - 需要优雅关机?用
signal.Notify()捕获SIGINT/SIGTERM,然后调db.Close(),别再做其他事 - 监控是否卡住:
db.Stats().WritesPending > 0说明还有未完成写入,Close()会等它们完成,但超时风险需留意
Dir/ValueDir 路径、事务提交、ValueCopy、Close() 这四点,漏任何一个,轻则数据不落盘,重则文件损坏无法恢复。尤其注意 ValueCopy —— 它不像 Value() 那样“看起来能用”,而是唯一安全的读取方式。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











