批量INSERT比单条快5–20倍,因减少网络往返、日志刷盘和索引更新;需控制单批100–500行,避免超max_allowed_packet等限制;推荐用pgx.Batch等安全高效方式实现。

为什么 INSERT INTO ... VALUES (...), (...), (...) 比循环单条 INSERT 快得多
单条 INSERT 每次都触发一次网络往返、一次事务日志刷盘、一次索引更新,而批量拼接后一次提交,能显著减少这些开销。PostgreSQL 和 MySQL 都对多值 INSERT 做了深度优化,实测 1000 行数据,批量写入通常比逐条快 5–20 倍。
但要注意:不能无限制拼接。MySQL 默认 max_allowed_packet 通常为 4MB,PostgreSQL 的 statement_timeout 和内存限制也会制约单条语句长度。建议单批控制在 100–500 行之间,具体按字段数和平均行大小反推。
- 字段越多、文本越长,单批行数应越少
- 使用
pgx(PostgreSQL)或mysql驱动时,避免手动字符串拼接 SQL——用参数化批量插入 - SQLite 不支持多值
INSERT的参数化形式(如VALUES (?, ?), (?, ?)),需改用INSERT ... SELECT或启用扩展
用 pgx.Batch 实现安全高效的 PostgreSQL 批量写入
pgx 的 Batch 机制不是简单拼 SQL,而是复用连接、预编译语句、批量发送参数,规避 SQL 注入且性能接近原生 COPY。它内部按配置的 BatchSize 分片,自动处理失败重试边界(但不自动回滚整批)。
conn, _ := pgx.Connect(ctx, connString)
defer conn.Close(ctx)
b := &pgx.Batch{}
for _, u := range users {
b.Queue("INSERT INTO users(name, email) VALUES($1, $2)", u.Name, u.Email)
}
br := conn.SendBatch(ctx, b)
defer br.Close()
for i := 0; i < len(users); i++ {
_, err := br.Exec()
if err != nil {
// 注意:此处 err 仅对应第 i 个语句,非整批
log.Printf("failed on item %d: %v", i, err)
}
}
- 不要在循环里调用
br.Exec()后立刻处理结果——这会退化成类逐条行为;应先全部Exec()完再统一检查 - 若需原子性(整批成功或全失败),必须显式开启事务:
tx, _ := conn.Begin(ctx),再用tx.SendBatch() -
Batch不支持返回sql.Result.LastInsertId(),如需主键,改用RETURNING id并解析每条响应
MySQL 场景下慎用 exec.QueryRow 替代 exec.Exec 批量插入
有人误以为 “用 QueryRow 能拿到自增 ID 就更‘完整’”,但在批量场景中,这是严重误区:QueryRow 强制等待单条语句返回,彻底破坏批量并发能力,性能可能比纯 Exec 慢一个数量级。
立即学习“go语言免费学习笔记(深入)”;
正确做法是:不需要即时 ID → 用 Exec + 多值 INSERT;需要 ID → 改用 INSERT ... VALUES (...), (...) RETURNING id(MySQL 8.0.19+)或分批后查最大 ID 范围(需唯一有序字段)。
- MySQL 驱动(如
go-sql-driver/mysql)不支持多值INSERT的RETURNING,得靠业务层补查 - 如果表有唯一约束且写入可能冲突,优先用
INSERT IGNORE或ON DUPLICATE KEY UPDATE,避免事务因唯一键错误中断 - 开启
multiStatements=true参数虽支持多语句,但有注入风险且多数 ORM 不兼容,不推荐
真正影响吞吐的隐藏瓶颈:事务提交频率与连接池设置
即使用了批量插入,如果每批都开新事务或连接池太小,性能仍上不去。典型表现是 CPU 利用率低、数据库连接数打满、pg_stat_activity 显示大量 idle in transaction。
- 把多批写入包在一个事务里(如 10 批 × 200 行 = 2000 行/事务),可减少 WAL 刷盘次数;但事务太大又增加锁持有时间和 OOM 风险
-
database/sql的SetMaxOpenConns建议设为数据库允许的最大连接数 × 0.8;SetMaxIdleConns至少等于并发批量协程数 - PostgreSQL 中,
synchronous_commit = off可大幅提升写入速度(牺牲极小概率崩溃丢数据),测试环境可开,生产需权衡
批量写入的复杂点从来不在“怎么拼 SQL”,而在于平衡事务粒度、连接复用、错误恢复和数据库侧资源水位——调参前务必用 EXPLAIN ANALYZE 或 pg_stat_statements 看真实执行计划。











