Go database/sql 怎样按批次提交写入
Go 用 database/sql 批量写入时,最稳妥的组织方式是“一个批次对应一个事务”:先按固定数量切出记录,在这个事务里复用一条预编译语句,批内任意写入失败就回滚,全部成功才提交。这样既不会让一笔大事务长期占住连接,也不会把每一行都拆成一次提交。
官方资料:https://pkg.go.dev/database/sql
本文只讨论事务范围、预编译语句和资源释放,不绑定某个数据库驱动的方言或性能数字。
为什么批次边界要落在事务上
*sql.DB 是由标准库管理的数据库句柄,内部负责连接池;而 sql.Tx 表示一次事务范围。批量写入如果从头到尾只使用一个事务,数据量变大后,锁、日志和连接占用都会跟着扩大;如果每行单独提交,又会让提交开销重复出现。
把记录切成若干批次后,边界就变得清楚:批次内的写入共享同一个 sql.Tx,批次之间互不共用事务。成功边界是 Commit,失败边界是 Rollback,下一批重新建立自己的事务对象。

一个批次怎样复用事务和预编译语句
实现时可以把“处理一个批次”封装成闭包或小函数。事务在批次函数内部创建,语句也在事务内部准备;不要把事务级 Stmt 跨批次保存,因为它的生命周期应当跟随当前事务。
// InsertProducts 按 batchSize 分批写入,每批独立提交事务。
func InsertProducts(ctx context.Context, db *sql.DB, rows []Product, batchSize int) error {
if batchSize len(rows) {
end = len(rows) // 最后一批允许不足 batchSize。
}
batchErr := func() (err error) {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin batch [%d:%d]: %w", start, end, err)
}
committed := false
defer func() {
if !committed {
_ = tx.Rollback() // 准备或写入失败时撤销当前批次。
}
}()
stmt, err := tx.PrepareContext(ctx,
"INSERT INTO products (id, name, price) VALUES (?, ?, ?)",
)
if err != nil {
return fmt.Errorf("prepare batch [%d:%d]: %w", start, end, err)
}
defer stmt.Close() // 语句跟随当前事务释放,不泄漏资源。
for _, row := range rows[start:end] {
if _, err = stmt.ExecContext(ctx, row.ID, row.Name, row.Price); err != nil {
return fmt.Errorf("insert id %d: %w", row.ID, err)
}
}
if err = tx.Commit(); err != nil {
return fmt.Errorf("commit batch [%d:%d]: %w", start, end, err)
}
committed = true
return nil
}()
if batchErr != nil {
return batchErr // 当前批次失败后停止,避免继续写入后续批次。
}
}
return nil
}
这里的 defer 只负责兜底回滚,成功提交后由 committed 阻止重复回滚;stmt.Close 则保证预编译语句不会随着批次循环不断累积。最后一批直接使用实际的 end,不需要为了凑满批次补空数据。
出错时怎样保住事务边界
需要重点区分三类错误:事务无法开始时,当前批次尚未写入;语句准备或某一行执行失败时,当前批次应回滚;提交失败时,调用方应把错误向上返回,不要擅自把这一批标记成成功。示例使用 ExecContext 让调用方可以通过上下文取消较慢的写入。

不要只在循环外写一次 defer tx.Rollback() 就忽略提交结果,也不要在事务中途遇到错误后继续执行下一行。错误一旦返回,当前批次的结论就应固定为失败;如果业务需要重试,应由上层根据错误类型和幂等设计决定,而不是在这个函数里无条件重放。
批次大小和资源释放怎么取舍
批次大小没有适用于所有数据库的固定数值,可以先从一个容易观察的中等范围开始,再依据单批耗时、锁持有时间、事务日志和驱动参数限制调整。更重要的是保持边界一致:批次切片、事务、预编译语句和提交结果应属于同一个生命周期。
| 关注点 | 建议 | 原因 |
|---|---|---|
| 事务范围 | 每个批次独立 BeginTx/Commit | 避免单个事务无限扩大 |
| 语句复用 | 批内 PrepareContext 一次 | 避免每行重复准备 |
| 上下文 | 为整批设置合理截止时间 | 取消时能结束数据库操作 |
| 连接池 | 结合并发批次数设置上限 | 批量写入仍会占用连接 |
| 驱动参数 | 确认占位符和批量 SQL 限制 | database/sql 不替驱动统一方言 |
如果批次很大,优先观察单批失败后的重试成本;如果批次很小,优先观察提交次数和连接占用。文章中的事务边界是结构性建议,不代表某个驱动或数据库的实测性能保证。
常见问题
可以把全部记录放进一个事务吗?
可以,但只适合数据量可控、锁和日志压力可接受的场景。需要长时间运行或允许部分批次成功时,按批次提交更容易控制失败范围。
每一行都调用 PrepareContext 可以吗?
不建议。重复准备会增加开销,也让资源生命周期更难管理;对同一批次的固定 INSERT,通常在批次事务内准备一次并循环执行即可。
Commit 返回错误后还能自动再提交吗?
不要在函数内部无条件重试。提交错误可能意味着服务端状态未知,应先记录批次标识和错误,由上层结合唯一键、幂等策略和驱动文档决定后续动作。
总结
Go database/sql 的批量写入可以用四个动作稳定下来:切分批次、批内开启事务并复用语句、失败立即回滚、成功才提交。配合 context.Context、stmt.Close 和明确的批次大小,写入逻辑就能在吞吐、失败范围和资源释放之间保持可解释的边界。
Go time.Ticker 怎样避免停止后继续泄漏
- 上一篇
- Go time.Ticker 怎样避免停止后继续泄漏
- 下一篇
- Go time.Duration 怎样安全转换毫秒参数
-
- Golang · Go教程 | 27分钟前 | Go 类型断言 plugin 动态插件 lookup plugin.Open
- Go plugin 怎样判断动态符号兼容性
- 150浏览 收藏
-
- Golang · Go教程 | 33分钟前 |
- Go compress/gzip 怎样根据客户端协商返回压缩响应
- 238浏览 收藏
-
- Golang · Go教程 | 39分钟前 | 标准库 · 测试 · go · IO · 单元测试 bytes.Buffer io.Writer Go io.Discard 输出注入
- Go io.Discard 怎样在测试中屏蔽可选输出
- 454浏览 收藏
-
- Golang · Go教程 | 41分钟前 | 网络编程 · 标准库 · go · Socket · SO_REUSEADDR Go net.ListenConfig Control RawConn socket选项
- Go net.ListenConfig 怎样设置 socket 选项
- 387浏览 收藏
-
- Golang · Go教程 | 46分钟前 |
- Go io.Pipe 怎样让消费者感知生产端错误
- 185浏览 收藏
-
- Golang · Go教程 | 52分钟前 |
- Go os/exec 怎样设置命令工作目录
- 360浏览 收藏
-
- Golang · Go教程 | 55分钟前 |
- Go slog.Attr.Equal 判断结构化字段是否一致的实现方式
- 371浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go html/template 怎样为 URL 属性限制协议
- 199浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go regexp.Regexp 怎样读取字面前缀
- 423浏览 收藏
-
- Golang · Go教程 | 1小时前 | 标准库 · Go教程 · 安全编程 · Go 证书校验 crypto/x509 CertPool 证书链 VerifyOptions
- Go crypto/x509 怎样校验证书链
- 486浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go crypto/tls 怎样加载多证书配置
- 434浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 528次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 586次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 613次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 565次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 388次使用
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- go语言中的defer关键字
- 2023-02-17 150浏览
-
- Golang中Interface接口的三个特性
- 2023-01-07 394浏览
-
- go语言中函数与方法介绍
- 2023-01-07 297浏览
-
- go语言数据类型之字符串string
- 2022-12-30 321浏览

