Go Badger 怎么批量写入并处理 Flush 错误
Go Badger 的批量写入关键不在于把更多的 Set 塞进一个对象,而在于把“收集写入”和“提交写入”分开处理。通常用 db.NewWriteBatch() 创建批次,在批次内逐条调用 Set 或 SetEntry,最后只把 Flush() 当作提交点;每一次写入和最终刷新都要保留错误。
官方地址:https://github.com/dgraph-io/badger/
本文只讨论 Badger v4 的 WriteBatch、Flush、Cancel 和错误处理,不把它扩展成数据库选型或通用事务教程。
先分清 Set 错误和 Flush 错误
WriteBatch.Set 的错误表示当前批次在收集数据时已经遇到问题,例如输入不符合 Badger 的写入约束;Flush 的错误则表示提交阶段返回了错误,或者返回了批次之前已经记录的错误。官方 API 还说明,一旦批次已经记录错误,后续不会继续执行提交。
因此,下面两件事不能混为一谈:
- 某条记录调用
Set失败:停止继续追加,记录这条输入的位置。 - 所有
Set都成功但Flush失败:把整个批次视为未完成,保留批次边界,再按业务策略恢复。

用有限大小的 WriteBatch 收集写入
批量大小不要只按记录条数决定,还要考虑单条 value 的大小和失败后的恢复成本。可以先用固定数量作为业务层分块边界,再通过 SetMaxPendingTxns 调整待处理事务上限。该方法应在开始写入前调用,官方文档给出的默认值是 16。
下面的示例将一批输入转换为 Badger 写入。示例故意把批次的起始游标交给调用方保存,这样调用方可以在外层记录“哪一批已经完成”,而不是把恢复状态藏在数据库内部。
package batchwrite
import (
"fmt"
badger "github.com/dgraph-io/badger/v4"
)
type Item struct {
Key string
Value []byte
}
// writeOneBatch 只负责收集并提交一个有限批次。
// 调用方应在 Flush 成功后再推进游标,避免把未完成批次误记为成功。
func writeOneBatch(db *badger.DB, items []Item, maxPending int) error {
wb := db.NewWriteBatch()
// 如果中途提前返回,Cancel 负责释放尚未提交的批次状态。
defer wb.Cancel()
if maxPending > 0 {
// 该上限应在第一次 Set 前设置,避免批次已经开始后再改变策略。
wb.SetMaxPendingTxns(maxPending)
}
for index, item := range items {
if item.Key == "" {
// 空键属于输入错误,不应继续把后续记录追加到同一批次。
return fmt.Errorf("item %d has empty key", index)
}
if err := wb.Set([]byte(item.Key), item.Value); err != nil {
// Set 错误要立即返回,外层可以记录当前批次和记录下标。
return fmt.Errorf("set item %d (%s): %w", index, item.Key, err)
}
}
// Flush 是批次提交点;只有它成功,外层才应推进批次游标。
if err := wb.Flush(); err != nil {
return fmt.Errorf("flush %d items: %w", len(items), err)
}
return nil
}
这个函数没有在内部自动重试。原因是 Flush 已经是提交边界,遇到错误时直接复制同一批次并重试,可能让调用方误以为所有记录都处于同一种状态。是否重试应由外层根据错误、幂等键和持久化游标决定。
把 Flush 当作批次提交点
使用 WriteBatch 时,最容易遗漏的是“创建了批次但既没有 Flush,也没有 Cancel”。官方文档明确提醒,这会让事务 oracle 无法清理行提交时间映射,长期运行可能造成内存持续增长。因此,defer wb.Cancel() 应紧跟在创建批次之后;正常路径仍然显式调用 Flush。
建议外层保存三个字段:
| 字段 | 用途 | 推进时机 |
|---|---|---|
| batch_start | 本次批次的输入起点 | 开始处理前确定 |
| batch_end | 本次批次的输入终点 | 按固定数量或字节预算切分 |
| status | 记录 pending、success 或 failed | 只有 Flush 成功后写 success |
这样即使应用在循环中途退出,也能从最后一个未完成批次开始处理,而不是凭“已经调用过多少次 Set”猜测进度。

Flush 出错时怎么恢复
恢复策略的核心是让每条输入拥有稳定、可重复计算的业务键。例如把订单号、对象版本或导入文件中的唯一行号编码到 key 中,而不是每次重试都生成新的随机 key。这样即使外层需要重新处理一个失败批次,也不会因为随机键产生一组无法覆盖的重复数据。
type Checkpoint struct {
Start int
End int
}
// writeWithCheckpoint 只在整个批次 Flush 成功后返回新的游标。
// checkpoint 的持久化应由调用方完成,并与业务状态更新采用一致的顺序。
func writeWithCheckpoint(db *badger.DB, all []Item, start, size int) (Checkpoint, error) {
if start >= len(all) {
// 没有剩余输入时,返回空范围而不是创建空批次。
return Checkpoint{Start: start, End: start}, nil
}
end := start + size
if end > len(all) {
// 最后一批只覆盖剩余数据,不人为填充占位记录。
end = len(all)
}
if err := writeOneBatch(db, all[start:end], 16); err != nil {
// 出错时保留原 start,下一次由调用方按幂等键决定是否恢复。
return Checkpoint{Start: start, End: start}, err
}
return Checkpoint{Start: start, End: end}, nil
}
如果应用需要把 Badger 写入和外部检查点放进同一个不可分割的业务动作,就不能只依靠这个内存结构体;应把检查点设计成可重复写入的记录,并明确“Badger 成功、检查点失败”或相反顺序下的补偿方案。本文的示例只保证批次边界清楚,不声称跨系统事务。
常见误区和一张速查表
- 只检查最后一次
Flush:前面的Set已经失败时,批次不会按预期提交。 - 把
Cancel当成回滚:官方说明已提交的写入仍会继续生效,Cancel 主要用于清理未完成的批次状态。 - 无限增大单批数据:单次失败后的重试成本、内存占用和游标恢复范围都会变大。
- 用随机键重试:同一业务记录可能被写成多条,后续无法判断哪条是有效版本。
| 现象 | 处理方式 |
|---|---|
| Set 返回错误 | 停止当前批次,记录输入位置和原始错误。 |
| Flush 返回错误 | 不推进外部游标,保留批次边界,按幂等键决定恢复。 |
| 中途提前返回 | 确保已注册 Cancel,避免遗留未完成批次状态。 |
| 批次长期占用内存 | 缩小批次并合理设置 MaxPendingTxns,检查是否遗漏 Flush/Cancel。 |
总结一下:Badger 批量写入可以从 NewWriteBatch 开始,但可靠性取决于三个边界——每次 Set 的错误、最终 Flush 的提交结果,以及异常路径上的 Cancel。把批次游标和幂等键放在调用方,才有条件在 Flush 出错后安全恢复。
相关问题
WriteBatch 和普通事务 Set 有什么区别?
WriteBatch 面向一组批量写入,调用方最后显式 Flush;普通事务则围绕一次事务生命周期组织读写。选择哪一种,取决于你要处理的是批量导入边界,还是需要在事务内组合读取与写入。
Flush 成功后还需要调用 Cancel 吗?
代码可以保留创建后的 defer Cancel 作为提前返回的兜底,但真正的提交动作仍由 Flush 完成。不要把 Cancel 当作成功确认,也不要用它替代 Flush。
可以在 Flush 失败后直接再调用一次 Flush 吗?
不要把无条件重复 Flush 当作通用恢复方案。先记录错误和批次边界,再依据错误类型、幂等键和外部检查点设计恢复动作。
Go Badger 怎么使用前缀迭代器读取一组记录
- 上一篇
- Go Badger 怎么使用前缀迭代器读取一组记录
- 下一篇
- Go Badger 事务提交报冲突时应该怎样重新执行
-
- Golang · Go教程 | 6分钟前 |
- Go csvutil 怎么逐条解码 CSV 而不保留所有记录
- 327浏览 收藏
-
- Golang · Go教程 | 6分钟前 |
- Go csvutil 怎么为业务字段实现自定义文本转换
- 326浏览 收藏
-
- Golang · Go教程 | 6分钟前 |
- Go csvutil 怎么让 CSV 表头映射到指定结构体字段
- 152浏览 收藏
-
- Golang · Go教程 | 13分钟前 |
- Go Excelize 怎么给单元格添加下拉数据校验
- 313浏览 收藏
-
- Golang · Go教程 | 20分钟前 |
- Go Excelize 怎么流式写入大量工作表行
- 117浏览 收藏
-
- Golang · Go教程 | 25分钟前 |
- Go go-toml 怎么拒绝配置中未声明的字段
- 158浏览 收藏
-
- Golang · Go教程 | 26分钟前 |
- Go go-toml 怎么为业务类型实现自定义 TOML 解码
- 303浏览 收藏
-
- Golang · Go教程 | 30分钟前 | go · 配置文件 · 结构体切片 Go go-toml TOML表数组 toml.Unmarshal toml.Decoder
- Go go-toml 怎么把 TOML 表数组解码为结构体切片
- 106浏览 收藏
-
- Golang · Go教程 | 38分钟前 | 环境变量 Go 嵌套配置 Koanf TransformFunc
- Go Koanf 怎么把环境变量名称转换成嵌套配置键
- 116浏览 收藏
-
- Golang · Go教程 | 41分钟前 |
- Go Koanf 怎么只把配置的一个子树映射到结构体
- 112浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 460次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 522次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 541次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 488次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 307次使用
-
- Go代码规范错误处理示例经验总结
- 2022-12-23 278浏览
-
- 分析Go错误处理优化go recover机制缺陷
- 2023-01-01 483浏览
-
- Go 错误处理实践总结示例
- 2023-01-07 291浏览
-
- Go程序员踩过的defer坑错误处理
- 2023-01-19 195浏览
-
- golang gorm错误处理事务以及日志用法示例
- 2023-02-16 412浏览

