必须显式配置连接池参数并限制goroutine并发数:设SetMaxOpenConns为QPS×耗时×1.5,用channel或semaphore节流,确保rows.Close()被调用,避免事务对象跨goroutine复用,查询结果通过带缓冲channel安全传递。

goroutine并发查询时连接池耗尽怎么办
Go的database/sql自带连接池,但直接为每个查询起一个goroutine却不控制并发数,很容易触发sql: connection refused或context deadline exceeded。根本原因不是goroutine本身有问题,而是底层连接被瞬间打爆。
实操建议:
- 永远用
db.SetMaxOpenConns(n)显式限制最大打开连接数(默认0=无限制),建议设为预期峰值QPS × 平均查询耗时(秒)× 1.5,例如QPS 100、平均耗时200ms → 设为30较稳妥 - 用
semaphore或带缓冲的channel做并发节制,别依赖连接池自动兜底 - 确保
rows.Close()被调用——哪怕用defer rows.Close(),否则连接不会归还池中
多个goroutine查同一张表要不要加锁
不需要。MySQL/PostgreSQL等数据库本身支持高并发读,SELECT是无锁操作(MVCC机制保证一致性)。Go侧对*sql.DB对象也无需额外加锁——它本身就是并发安全的。
但要注意:
立即学习“go语言免费学习笔记(深入)”;
-
*sql.Tx不是并发安全的,不能在多个goroutine里共用同一个事务对象执行Query/Exec - 如果查询逻辑里有共享的非DB状态(比如缓存map、计数器),那才需要
sync.Mutex或sync.Map - 避免在goroutine里直接用闭包捕获循环变量,常见错误:
for _, id := range ids { go func() { db.QueryRow("SELECT ...", id) }() }→ 所有goroutine都用最后一个id值
如何安全地把查询结果传回主goroutine
别用全局变量或未同步的map写入。推荐通道(channel)+结构体封装:
<pre class="brush:php;toolbar:false;">type UserResult struct {
ID int
Name string
Err error
}
results := make(chan UserResult, len(ids))
for _, id := range ids {
go func(id int) {
var u User
err := db.QueryRow("SELECT id, name FROM users WHERE id = ?", id).Scan(&u.ID, &u.Name)
results <- UserResult{ID: u.ID, Name: u.Name, Err: err}
}(id)
}
// 主goroutine收集
for i := 0; i < len(ids); i++ {
r := <-results
if r.Err != nil {
log.Printf("query failed for id %d: %v", r.ID, r.Err)
continue
}
// 处理r
}
注意点:
- 通道缓冲大小设为
len(ids)可避免goroutine阻塞在发送端 - 不要在goroutine里直接
log.Fatal或panic,会杀死整个程序;错误应通过channel或返回值传递 - 若需按原顺序接收结果,改用索引通道或
sync.WaitGroup+ 切片索引写入
为什么用了goroutine反而比串行还慢
典型原因是I/O没真正并行,或上下文切换开销反超收益。常见于:
- 单条查询本身就很重(比如全表扫描+排序),并发只是让数据库更忙,CPU/IO都在等磁盘
- 使用了
context.WithTimeout但超时时间设得太短,大量goroutine提前退出重试 - 误把CPU密集型处理(如JSON解析、加密)塞进goroutine,却没用
runtime.GOMAXPROCS配足P,或没考虑GIL类问题(虽然Go没有GIL,但单核CPU上纯计算仍无法并行) - 数据库连接池太小,goroutine大部分时间在
waiting for connection
验证方法:用go tool trace看goroutine阻塞在netpoll还是select,再结合SHOW PROCESSLIST看数据库连接实际状态。
真正要并发查数据库,得先确认瓶颈在网路往返或数据库服务端响应,而不是本地CPU或连接池配置。否则加goroutine只是把问题从“慢”变成“崩”。


















