db.Stats() 返回实时连接池水位:InUse(在用连接数)、Idle(空闲连接数)、WaitCount(累计等待次数);InUse接近MaxOpenConnections且Idle为0时表明瞬时拥塞,WaitCount持续增长则确为连接池瓶颈。

怎么用 db.Stats() 看连接池实时水位
别靠猜,db.DB().Stats() 是唯一能告诉你“现在池子到底有多满”的运行时指标。它返回的结构体里三个字段最关键:InUse(正在被业务 goroutine 持有的连接数)、Idle(空闲待复用的连接数)、WaitCount(因池满而排队等待连接的总次数)。
实操建议:
- 在 HTTP handler 开头或中间件里定期打点,比如每 10 秒
log.Printf("db stats: %+v", db.DB().Stats()) -
InUse长期 >95% 且Idle接近 0:说明池子太小,请求在等连接 -
WaitCount持续上涨:不是瞬时毛刺,而是真实瓶颈,必须扩容SetMaxOpenConns - 注意:这个统计是快照,不是采样率;高并发下建议配合 Prometheus +
gorm_db_stats自定义指标
模拟真实并发压测时怎么避免干扰因素
用 ab 或 hey 直接压接口,但结果不准——因为 Go HTTP server、gin 中间件、日志、甚至 GC 都会掺杂延迟。真正要测的是“数据库连接池吞吐能力”,就得绕过上层,直击 *gorm.DB。
实操建议:
- 写一个独立压测函数,用
sync.WaitGroup启动 N 个 goroutine,每个只做db.First(&user)这类轻量查询 - 禁用 GORM 日志:
gorm.Config{Logger: logger.Default.LogMode(logger.Silent)},否则日志 IO 会先打满磁盘 - 确保压测期间没有其他服务连同一数据库实例(比如监控脚本、定时任务),否则
SHOW PROCESSLIST看到的连接数不干净 - 压测前先
db.DB().Ping(),排除网络或认证问题导致的假性失败
怎么验证是不是连接池瓶颈而不是 SQL 本身慢
看到 P99 延迟飙升,第一反应不该是调大 SetMaxOpenConns,而是确认问题源头。连接池瓶颈的表现和慢查询完全不同。
关键区分点:
- 如果
db.DB().Stats().WaitCount上涨 +InUse == SetMaxOpenConns+ 数据库SHOW PROCESSLIST显示大量sleep状态连接:是池子满了,新请求卡在等连接 - 如果
InUse很低(比如 500ms,且EXPLAIN显示全表扫描:是 SQL 或索引问题,扩池没用 - MySQL 报
Too many connections错误:说明SetMaxOpenConns超过了数据库服务端max_connections,不是 GORM 池的问题,是配错上限了 - PostgreSQL 报
too many clients already:同理,检查postgresql.conf的max_connections和你设的SetMaxOpenConns是否匹配
生产环境上线前必须做的三件事
负载测试不是一次性动作,而是上线前的强制检查项。很多团队漏掉其中一环,上线后半夜就被告警叫醒。
必须执行:
- 用
SELECT * FROM pg_stat_activity(PG)或SHOW PROCESSLIST(MySQL)确认数据库侧当前连接数与db.DB().Stats()对得上——对不上说明有连接泄漏(比如 defer db.Close() 忘写了) - 把
SetConnMaxLifetime设为比数据库wait_timeout小至少 5 分钟,否则你会看到大量read: connection reset by peer,这是连接被服务端静默断开后 GORM 还在复用 - 压测峰值 QPS 后,等 2 分钟再查
db.DB().Stats():如果Idle一直不回收,说明SetConnMaxIdleTime没生效(注意:Go 1.15+ 才支持该方法,老版本只能靠SetConnMaxLifetime间接控制)
真实瓶颈往往藏在 WaitCount 持续增长却没人看的地方,或者 SetConnMaxLifetime 设成 0 导致连接在防火墙后面悄悄断开又不断重连。这些细节不盯住,压测数据再好看,上线照样崩。


















