不能一次性insert几万条数据,因会触发packet too large、内存溢出、事务日志暴涨、锁表过久等错误;分批是必须的容错手段,mysql建议200–500行/批,postgresql 500–1000行/批,sqlite 100–200行/批,需结合单行大小、字段类型、网络延迟压测确定。

为什么不能一次性 INSERT 几万条数据?
直接用 db.Exec("INSERT INTO ... VALUES (...),(...),...") 拼接几万行值,大概率触发数据库报错:MySQL 会抛 Packet too large,PostgreSQL 可能卡在参数绑定或内存溢出,SQLite 直接拒绝。更隐蔽的问题是事务日志暴涨、锁表时间过长、OOM 或连接超时。分批不是“优化”,而是必须的容错手段。
怎么确定每批多少条才安全?
没有全局最优解,取决于你的数据库类型、单行数据大小、字段类型(尤其是 TEXT/JSONB)、网络延迟和事务隔离级别。实操中建议从以下值起步并压测:
- MySQL:200–500 行/批(
max_allowed_packet默认 4MB,按平均 1KB/行反推) - PostgreSQL:500–1000 行/批(受
work_mem和 WAL 写入压力影响更大) - SQLite:100–200 行/批(单事务写入性能衰减明显)
关键原则:单批 SQL 字符串长度别超 1MB;单次事务执行时间控制在 200ms 内;避免在高并发写入时把 autocommit 关掉太久。
Go 里用 sql.Tx 分批提交的典型写法
别用 db.BeginTx(ctx, &sql.TxOptions{}) 包整个大循环——那等于没分批。正确做法是每个批次单独开事务、执行、提交/回滚,再进下一批。示例逻辑如下:
// 假设 data 是 []User 类型切片
const batchSize = 300
for i := 0; i
<p>注意点:<code>min()</code> 需自己定义(Go 1.21+ 可用 <code>min(i+batchSize, len(data))</code>);<code>PrepareContext</code> 在事务内复用能省解析开销;但若字段太多或结构动态,改用 <code>tx.Stmt(stmt)</code> 复用更稳。</p>
<h3>批量插入失败时如何定位具体哪条出错?</h3>
<p>用 <code>Exec</code> 批量执行时,错误只返回一次,无法知道第几条崩了。真要精确追踪,得放弃预编译语句,改用循环 + 单条 <code>Exec</code>(牺牲一点性能换可观测性):</p>
<pre class="brush:php;toolbar:false;">for i, u := range batch {
_, err := tx.ExecContext(ctx, "INSERT INTO users(name, email) VALUES (?, ?)", u.Name, u.Email)
if err != nil {
log.Printf("batch[%d] failed at index %d: %v", batchIndex, i, err)
tx.Rollback()
return err
}
}
或者更实用的做法:先用 Validate() 做前置校验(空值、长度、格式),再进 DB 层;数据库层统一捕获 mysql.MySQLError 或 pgconn.PgError,提取 SQLState 和 Code 做分类重试(如唯一键冲突可跳过,约束失败需告警)。
真正麻烦的从来不是“怎么分批”,而是分批后怎么让失败不中断整体流程、怎么让重试不重复写、怎么让监控看到每批耗时和成功率——这些得结合 context 超时、log field 标注 batch_id、以及外部指标上报来做。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











