会。默认 sql.DB 连接池小(常为0或2),数十 goroutine 并发 Exec 会导致连接争抢、context deadline exceeded 或 too many connections;应设 db.SetMaxOpenConns 并用带缓冲 channel 限流。

goroutine 并发插入时,数据库连接会爆掉吗?
会。默认 sql.DB 的连接池大小有限(通常 0 或 2),开几十个 goroutine 同时 Exec,大量协程卡在获取连接上,甚至触发 context deadline exceeded 或 too many connections 错误。
关键不是“能不能并发”,而是“怎么控制并发节奏”。别直接起 100 个 goroutine 调 db.Exec。
- 用
db.SetMaxOpenConns(n)主动设上限(比如 20),避免打满数据库 - 用带缓冲的 channel 控制同时执行的 goroutine 数量(如
sem := make(chan struct{}, 10)) - 每个插入任务前先
sem ,完成后 <code>
用 sync.WaitGroup + channel 控制并发插入更稳妥
比裸起 goroutine + go func(){}() 更易管理生命周期和错误收集。
示例核心逻辑:
立即学习“go语言免费学习笔记(深入)”;
var wg sync.WaitGroup
sem := make(chan struct{}, 5) // 最多 5 个并发
errCh := make(chan error, len(data))
<p>for _, item := range data {
wg.Add(1)
go func(d MyRow) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()</p><pre class="brush:php;toolbar:false;"> _, err := db.Exec("INSERT INTO users(name, age) VALUES(?, ?)", d.Name, d.Age)
if err != nil {
errCh <- err
return
}
}(item)}
go func() { wg.Wait() close(errCh) }()
// 收集错误(可选) for err := range errCh { log.Printf("insert failed: %v", err) }
注意:这里用的是 db.Exec,不是事务。如果需要原子性,得包进单个事务里 —— 那就不能并发插入了。
大批量插入该用事务 + 批量语句,而不是 goroutine
真正提升吞吐的不是并发 goroutine,是减少 round-trip 和 SQL 解析开销。1000 行数据,1000 次 INSERT 远慢于 1 次 INSERT INTO ... VALUES (...), (...), (...)。
- MySQL 支持
INSERT INTO t(a,b) VALUES (?,?),(?,?),(?,?),一次最多加几千值(受max_allowed_packet限制) - PostgreSQL 推荐用
pgx.CopyFrom或UNNEST,比拼接 VALUES 快得多 - SQLite 可用
PRAGMA synchronous = OFF+ 事务包裹批量写入
goroutine 在这里只适合做「分片」:把 10 万行切分成每批 1000 行,再用 5 个 goroutine 各自提交一个事务。不是每行一个 goroutine。
别忘了 context 控制超时和取消
批量插入可能卡住(网络抖动、锁等待、大事务),必须加 context。
- 用
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - 所有
db.ExecContext、tx.PrepareContext都传这个ctx - 出错或超时时,
cancel()能让正在等待的连接快速失败,不堆积
漏掉 context 是线上批量任务 silent hang 的最常见原因 —— 看似在跑,其实卡在某次 db.QueryRowContext 上不动了。


















