真正有效的并发查询核心是“可控批量+连接复用+结果聚合”,而非无脑go启动大量db.Query;需分组批量查询、限流、超时控制、连接池调优、Context传递及数据库索引与执行计划优化。

Go 里用协程并发查数据库,不加控制反而更慢,甚至拖垮数据库或触发连接池耗尽。真正有效的并发查询,核心是“可控批量 + 连接复用 + 结果聚合”,不是无脑 go 启一堆 db.Query。
为什么直接 go db.Query 会出问题
常见错误是循环里对每个 ID 起一个 goroutine 查库:
- 每个
db.Query都可能抢新连接,MaxOpenConns很快打满,后续请求排队等待 - 没设上下文超时,某个慢查询卡住整个 goroutine,无法 cancel
- 结果没归并,还得额外同步收集,channel 多路接收写法容易漏数据或死锁
- 数据库侧看到大量短连接、小查询,执行计划缓存命中率低,索引也难有效利用
用批量 + 并发组合代替单点并发
先按业务逻辑把要查的 ID 或条件分组(比如每 100 个一组),再对每组并发查——既减少总请求数,又控制并发压力:
- 分组用
sqlx.In或手动拼IN参数,避免手拼 SQL 注入风险 - 每组查完用
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)控制超时 - 并发数严格限制:用
semaphore.NewWeighted(5)(golang.org/x/sync/semaphore),别用无缓冲 channel 做限流 - 查完结果统一 append 到一个切片,别在 goroutine 里直接往共享 map 写——避免加锁或竞态
连接池和上下文必须显式调优
默认 sql.DB 配置扛不住并发查,不调就等于没并发:
立即学习“go语言免费学习笔记(深入)”;
-
db.SetMaxOpenConns(50):按预估 QPS × 平均查询耗时(秒)算,比如 100 QPS × 0.2s = 20,留余量设 50 -
db.SetMaxIdleConns(25):保证常有空闲连接可立即复用 -
db.SetConnMaxLifetime(5 * time.Minute):防止数据库主动断连后 Go 还往里发请求 - 所有查询必须走
QueryContext/QueryRowContext,且检查返回的err,不只是Scan错误
别忽略数据库端的配合
应用层并发再合理,数据库没配好也白搭:
- 确保查询字段有对应索引,特别是
IN里的字段;用EXPLAIN看是否走range或ref,不是ALL - PostgreSQL 注意
work_mem,批量IN查询可能触发磁盘排序;MySQL 检查max_allowed_packet是否够塞下大参数列表 - 如果查的是关联数据(如用户+订单),优先用
JOIN一次查出,而不是并发查主表再并发查子表——后者仍是 N+1 变体
最易被忽略的一点:并发查的前提是这些查询之间**无依赖、无状态共享、结果可乱序合并**。一旦某次查询结果要作为下一次的输入,或者需要严格顺序返回,强行并发只会增加复杂度和出错概率。


















