
为什么 db.SetMaxOpenConns 设太高反而拖慢请求
连接池开太多,不是并行能力变强,而是让数据库服务器和 Go 应用同时陷入资源争抢:MySQL 的 max_connections 很快被占满,PostgreSQL 的 backend 进程数暴涨,Go 侧则因锁竞争(sql.DB 内部的 mutex)导致 db.Query 调用卡在获取连接这一步。
- 默认值是 0(无上限),压测时极易触发雪崩——尤其当单次请求含多次
Query或Exec - 合理值 ≈ 后端数据库允许的并发活跃连接数 × 0.6~0.8,例如 MySQL
max_connections=200,这里设120比设300更稳 - 观察指标:压测中若
db.Stats().WaitCount持续上涨,说明连接不够用;但若db.Stats().MaxOpenConnections长期打满且db.Stats().Idle接近 0,大概率是设太高了,连接没释放或复用率低
db.SetMaxIdleConns 和 db.SetConnMaxLifetime 必须配对调
只调空闲连接数却不设连接寿命,会导致连接池里积压大量“僵死”连接——它们还挂着旧事务、过期 TLS 会话,或已被数据库主动断开(如 MySQL 的 wait_timeout),下次复用直接报 driver: bad connection。
-
db.SetMaxIdleConns(20)是安全的起点,但必须同步设db.SetConnMaxLifetime(30 * time.Second),让连接定期轮换 - PostgreSQL 建议把
ConnMaxLifetime设得比tcp_keepalive_time短(Linux 默认 7200s),否则空闲连接可能被中间网络设备静默断开 - 别设
ConnMaxLifetime = 0,这不是“永不过期”,而是禁用自动清理,等于放任僵尸连接堆积
压测时 database/sql 的真实瓶颈常不在 SQL 本身
用 go tool pprof 看火焰图,经常发现 40%+ 时间耗在 sync.(*Mutex).Lock 或 runtime.usleep —— 这说明连接池争抢或 DNS 解析/连接建立拖慢了整体吞吐,而不是 SQL 执行慢。
- 确认是否启用了连接复用:检查日志里有没有高频出现
net.Dial或tls.handshake,有就说明连接没复用上,可能是SetMaxIdleConns太小或ConnMaxLifetime太短 - MySQL 驱动注意加
timeout和readTimeout参数,避免单个慢查询拖垮整个池子,例如 DSN 末尾加&timeout=5s&readTimeout=5s - 压测工具本身也得用连接池(比如
hey -c 100要配-m POST并确保 HTTP 客户端复用 TCP 连接),否则你测的其实是网络建连性能
Go 1.19+ 的 db.SetMaxOpenConns 动态调整仍不安全
运行时调 db.SetMaxOpenConns 看似灵活,但实际会触发内部连接回收逻辑,可能在高负载下造成短暂连接短缺,且新旧连接数切换期间无法精确控制。
立即学习“go语言免费学习笔记(深入)”;
- 初始化阶段一次性设好,后续不要动——它不是配置热更新项
- 如果业务有明显峰谷(如凌晨批量任务),宁可启动两个独立
*sql.DB实例,各自配不同参数,用路由逻辑分发 - 别依赖
db.Stats()的瞬时值做自动扩缩容,它的统计是采样,且本身有锁开销
事情说清了就结束。真正卡住性能的,往往不是 max_open 或 idle 数字本身,而是这几个参数之间隐含的生命周期耦合关系——改一个,另外两个不动,大概率出问题。











