直接用 db.exec 批量插入会卡住,因其每次调用都触发独立网络往返和事务开销,即使传入千行数据也执行千次语句;应改用 pgx.batch(postgresql)或拼接多值 insert(mysql)提升吞吐。

为什么直接用 db.Exec 批量插入会卡住?
因为默认的 db.Exec 每次调用都走一次网络往返 + 事务开销,哪怕传入 1000 行数据,底层仍是 1000 次独立语句执行。PostgreSQL 的 pgx 或 MySQL 的 mysql 驱动在未显式启用批量模式时,不会合并参数或复用连接上下文。
常见现象:CPU 不高、内存平稳,但入库 QPS 卡在 200–500,pg_stat_activity 里看到大量 idle in transaction 或连接堆积。
- 必须关闭自动提交,显式控制事务生命周期
- 避免在循环里反复调用
db.Prepare—— 准备语句应复用,不是每批都重做 - PostgreSQL 推荐用
COPY FROM STDIN(通过pgx.Batch或pgconn.PgConn.CopyFrom),MySQL 推荐INSERT INTO ... VALUES (...), (...), (...)拼接(单条不超过 max_allowed_packet)
如何用 pgx.Batch 实现真正并行写入?
pgx.Batch 不是并发安全的,但它允许把多条语句攒成一批发给服务端,由 PostgreSQL 在单次 round-trip 中解析执行,吞吐能翻 3–8 倍。关键点在于:它不等于 goroutine 并发,而是「逻辑批」+「连接复用」。
实操要点:
- 一个
*pgx.Conn对应一个pgx.Batch,不要跨连接共享 batch 实例 - 每批大小建议 100–500 条(取决于单行数据体积;超过 1000 行可能触发 PostgreSQL 的 tuple size 检查或内存压力)
- 必须手动调用
batch.Queue(...)后再conn.SendBatch(ctx, batch),不能只 queue 不 send - 错误处理要检查
batchResults := batchExec.Results()的每个pgx.BatchResult.Err(),而非只看整体 error
示例片段:
batch := &pgx.Batch{}
for _, item := range items {
batch.Queue("INSERT INTO events (id, ts, payload) VALUES ($1, $2, $3)", item.ID, item.TS, item.Payload)
}
br := conn.SendBatch(ctx, batch)
defer br.Close()
for i := 0; i
<h3>MySQL 场景下怎么避免 <code>max_allowed_packet</code> 报错?</h3>
<p>拼接 <code>INSERT</code> 多值语句时,如果单批数据总长度超服务端 <code>max_allowed_packet</code>(默认 4MB),会直接报错 <code>ERROR 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytes</code>,且不会回滚已执行部分。</p>
<p>安全做法:</p>
- 入库前预估每行字节数(字符串字段按 UTF8 最大长度算,比如
varchar(255)按 255×3=765 字节估算),动态切分 batch - 不要依赖
SELECT @@max_allowed_packet—— 客户端驱动可能因编码、转义额外增加开销,留 20% 余量 - 禁用
multiStatements=true参数(易被注入,且 MySQL 8.0+ 默认拒绝) - 改用
LOAD DATA LOCAL INFILE(需服务端开启local_infile,且文件路径需客户端可读)—— 更快但部署约束多
为什么加了 goroutine 之后吞吐反而下降?
盲目起 100 个 goroutine 往数据库写,大概率触发连接池耗尽、锁竞争或服务端拒绝连接。Go 的 sql.DB 连接池默认 MaxOpenConns=0(无上限),但 PostgreSQL 服务端有 max_connections 限制(通常 100),MySQL 也有类似硬限。
真实有效的并发策略:
- 固定 worker 数 = 数据库连接池大小 × 1.5(例如 pool=20 → worker=30),用
errgroup.Group控制并发 - 每个 worker 独占一个
*sql.Conn(db.Conn(ctx)获取),避免多个 goroutine 争抢同一连接上的mutex - 批量大小和 worker 数要一起压测:worker 太多导致 context cancel 频繁;batch 太小则网络开销占比上升
- 务必设置
db.SetMaxIdleConns(20)和db.SetMaxOpenConns(20),防止连接泄漏或雪崩
真正卡点往往不在 Go 侧代码,而在 WAL 写入速度、磁盘 IOPS、索引数量(每多一个二级索引,写放大至少 ×2)。上线前一定要在同规格机器上用真实数据跑 10 分钟压测,看 pg_stat_bgwriter 的 buffers_written 和 checkpoints_timed 是否异常飙升。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











