当前位置:首页 > 文章列表 > Golang > Go教程 > Go database/sql 怎样按批次提交写入

Go database/sql 怎样按批次提交写入

来源:17golang原创 2026-10-18 21:05:21 0浏览 收藏

Go 用 database/sql 批量写入时,最稳妥的组织方式是“一个批次对应一个事务”:先按固定数量切出记录,在这个事务里复用一条预编译语句,批内任意写入失败就回滚,全部成功才提交。这样既不会让一笔大事务长期占住连接,也不会把每一行都拆成一次提交。

官方资料:https://pkg.go.dev/database/sql

本文只讨论事务范围、预编译语句和资源释放,不绑定某个数据库驱动的方言或性能数字。

为什么批次边界要落在事务上

*sql.DB 是由标准库管理的数据库句柄,内部负责连接池;而 sql.Tx 表示一次事务范围。批量写入如果从头到尾只使用一个事务,数据量变大后,锁、日志和连接占用都会跟着扩大;如果每行单独提交,又会让提交开销重复出现。

把记录切成若干批次后,边界就变得清楚:批次内的写入共享同一个 sql.Tx,批次之间互不共用事务。成功边界是 Commit,失败边界是 Rollback,下一批重新建立自己的事务对象。

输入记录切成批次后由 sql.DB 管理 sql.Tx 和 Stmt 并关联数据库写入的静态结构说明图
图1:批次与事务边界的静态结构说明图,不是截图或运行证据。

一个批次怎样复用事务和预编译语句

实现时可以把“处理一个批次”封装成闭包或小函数。事务在批次函数内部创建,语句也在事务内部准备;不要把事务级 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 让调用方可以通过上下文取消较慢的写入。

批次范围关联 ExecContext、错误返回、Rollback、Commit 和 Stmt.Close 的静态资源生命周期说明图
图2:批量写入的错误处理与资源生命周期静态说明图,不是截图或运行证据。

不要只在循环外写一次 defer tx.Rollback() 就忽略提交结果,也不要在事务中途遇到错误后继续执行下一行。错误一旦返回,当前批次的结论就应固定为失败;如果业务需要重试,应由上层根据错误类型和幂等设计决定,而不是在这个函数里无条件重放。

批次大小和资源释放怎么取舍

批次大小没有适用于所有数据库的固定数值,可以先从一个容易观察的中等范围开始,再依据单批耗时、锁持有时间、事务日志和驱动参数限制调整。更重要的是保持边界一致:批次切片、事务、预编译语句和提交结果应属于同一个生命周期。

关注点建议原因
事务范围每个批次独立 BeginTx/Commit避免单个事务无限扩大
语句复用批内 PrepareContext 一次避免每行重复准备
上下文为整批设置合理截止时间取消时能结束数据库操作
连接池结合并发批次数设置上限批量写入仍会占用连接
驱动参数确认占位符和批量 SQL 限制database/sql 不替驱动统一方言

如果批次很大,优先观察单批失败后的重试成本;如果批次很小,优先观察提交次数和连接占用。文章中的事务边界是结构性建议,不代表某个驱动或数据库的实测性能保证。

常见问题

可以把全部记录放进一个事务吗?

可以,但只适合数据量可控、锁和日志压力可接受的场景。需要长时间运行或允许部分批次成功时,按批次提交更容易控制失败范围。

每一行都调用 PrepareContext 可以吗?

不建议。重复准备会增加开销,也让资源生命周期更难管理;对同一批次的固定 INSERT,通常在批次事务内准备一次并循环执行即可。

Commit 返回错误后还能自动再提交吗?

不要在函数内部无条件重试。提交错误可能意味着服务端状态未知,应先记录批次标识和错误,由上层结合唯一键、幂等策略和驱动文档决定后续动作。

总结

Go database/sql 的批量写入可以用四个动作稳定下来:切分批次、批内开启事务并复用语句、失败立即回滚、成功才提交。配合 context.Context、stmt.Close 和明确的批次大小,写入逻辑就能在吞吐、失败范围和资源释放之间保持可解释的边界。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go time.Ticker 怎样避免停止后继续泄漏Go time.Ticker 怎样避免停止后继续泄漏
上一篇
Go time.Ticker 怎样避免停止后继续泄漏
Go time.Duration 怎样安全转换毫秒参数
下一篇
Go time.Duration 怎样安全转换毫秒参数
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    528次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    586次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    613次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    565次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    388次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码