直接用db.Exec插入万条数据慢得反常,因每次调用均触发网络往返、事务开销和SQL解析;批量插入通过合并操作、复用预编译语句及显式事务控制,可将10万行耗时从20+秒压至1–3秒。

为什么直接用 db.Exec 插入万条数据会慢得反常
因为每次调用 db.Exec 都触发一次网络往返 + 事务开销 + SQL 解析,哪怕用连接池也扛不住。实测插入 10 万行,单条 INSERT 可能耗时 20+ 秒;换成批量后通常压到 1–3 秒。
- PostgreSQL 推荐用
COPY FROM STDIN(最快),或拼接多值INSERT INTO ... VALUES (...), (...), (...) - MySQL 用
INSERT INTO ... VALUES (...), (...), (...)+bulk_insert_buffer_size调优 - SQLite 直接关事务:
db.Exec("BEGIN")→ 批量INSERT→db.Exec("COMMIT") - 别在循环里反复调用
stmt.Exec()—— 即使预编译,没批量仍无效
怎么写一个安全可控的批量插入函数
核心是控制每批行数(避免 OOM 或语句超长)、复用 sql.Stmt、显式事务管理。Go 标准库不提供“自动分批”,得自己切片。
- 按 1000–5000 行/批较稳妥(具体看字段数和单行大小)
- 用
strings.Builder拼接多值INSERT时,注意 SQL 注入 —— 必须用参数化($1, $2或?),不能字符串插值 - 示例片段:
for len(data) > 0 { batch := data[:min(1000, len(data))] _, err := tx.Stmt(stmt).ExecContext(ctx, toArgs(batch)...) if err != nil { return err } data = data[len(batch):] } - MySQL 的
LOAD DATA INFILE虽快,但要求文件在服务端磁盘,生产环境通常禁用
遇到 ERROR: 22001: value too long for type character varying(255) 怎么办
不是数据问题,是批量拼接时某列超长导致整批失败。单条插入能报错定位,批量却只报“第 X 行失败”,难排查。
- 入库前加校验:对关键字段做
utf8.RuneCountInString(s) <= 255(注意 MySQL 的varchar(255)是字符数,不是字节数) - 开启
log.Println记录每批首尾几条原始数据,出错时快速回溯 - 把批量拆得更小(比如 100 行/批)+ 设置重试上限,避免一错全崩
- PostgreSQL 可用
ON CONFLICT DO NOTHING绕过重复键冲突,但无法跳过类型错误
并发写入时为啥出现 pq: sorry, too many clients already
不是连接数设少了,而是批量逻辑没复用连接 —— 每批都新开 sql.Tx 或没正确 Close。
立即学习“go语言免费学习笔记(深入)”;
- 用
db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})开一个事务贯穿整批,别每个子批都开 - 确保
defer tx.Rollback()后有tx.Commit(),否则连接永远卡在“idle in transaction” - 连接池设置:
db.SetMaxOpenConns(20)+db.SetMaxIdleConns(10),避免瞬时打满 - 如果必须并发多批次(如分片导入),每 goroutine 用独立
sql.DB实例,别共用一个


















