db.Stats().OpenConnections 总是 0 是因为连接池惰性初始化,gorm.Open 仅创建实例,真实连接需显式调用 db.DB().Ping() 或首次查询(如 db.First)才能建立并填充初始连接,否则 Stats() 返回全零值。

为什么 db.Stats().OpenConnections 总是 0?
刚接入 Prometheus 时很多人发现 gorm_db_open_connections 指标一直为 0,根本告不了警。这不是指标没暴露,而是你没调用 db.DB().Ping() —— GORM 的连接池是惰性初始化的,gorm.Open 只返回 *gorm.DB 实例,底层 *sql.DB 的连接池直到第一次查询(如 db.First())或显式 Ping() 才真正建立并填充初始连接。不触发这一步,db.DB().Stats() 返回的全是零值。
实操建议:
- 服务启动后立即执行一次
db.DB().Ping(),失败要 panic 或重试,否则后续所有池指标都不可信 - 确保
prometheusplugin 已启用(GORM v2+),且在gorm.Open后注册,不是在init()阶段提前注册 - 检查
DB().Stats()是否被调用在Ping()之后,顺序错了就永远拿不到真实连接数
如何暴露 GORM 连接池核心指标?
GORM 官方 prometheus plugin 会自动注册以下关键指标,但必须满足两个前提:已调用 Ping(),且 plugin 初始化在 gorm.Open 之后。
核心指标包括:
-
gorm_db_open_connections:当前打开的总连接数(含空闲 + 正在使用) -
gorm_db_idle_connections:当前空闲连接数 -
gorm_db_in_use_connections:当前正在被使用的连接数(= open − idle) -
gorm_db_wait_count:连接获取等待次数(反映池是否打满) -
gorm_db_wait_duration_seconds_total:累计等待耗时(单位秒)
注意:plugin/prometheus 默认只暴露这些,不包含自定义 SQL 耗时或慢查询统计,后者需额外注册 callback。
连接池打满的告警规则怎么写?
光看绝对数值没意义,得结合配置阈值和增长趋势。重点盯三个信号:
- 连接使用率持续 > 90%:即
100 * (gorm_db_open_connections - gorm_db_idle_connections) / gorm_db_open_connections > 90,持续 2m - 等待计数突增:
rate(gorm_db_wait_count[5m]) > 10(5 分钟内平均每秒等待超 10 次) - 等待耗时上升:
rate(gorm_db_wait_duration_seconds_total[5m]) > 0.5(5 分钟内平均每秒等待耗时超 0.5 秒)
别只设单一阈值。比如 MaxOpenConns=50,但某次批量任务导致 open_connections 稳定在 48,idle=2,此时使用率 96%,但无等待——说明池够用但偏紧;而如果 idle=0 且 wait_count 开始上涨,才是真打满。两者告警优先级不同。
为什么 Grafana 看板里指标断崖式归零?
常见于服务重启后未触发 Ping(),或 DB().Stats() 被并发调用导致竞态(GORM v2.2.5 之前有此 bug)。更隐蔽的是:Prometheus 抓取周期(如 15s)与连接池空闲回收(SetConnMaxIdleTime(30m))叠加,导致某次抓取刚好落在所有连接被回收后的瞬间,open_connections 暂时归零。
排查要点:
- 确认
SetConnMaxIdleTime和SetConnMaxLifetime设置合理(推荐 30m 和 7h),避免连接被数据库端或客户端过早驱逐 - 在服务日志中加一行:启动后打印
db.DB().Stats(),验证初始值是否非零 - 检查 Prometheus targets 页面,确认该实例状态为 UP,且 Last Scrape 是成功的(不是 500 或 timeout)
最常被忽略的一点:GORM 的 prometheus plugin 默认不采集连接创建/关闭事件,所以你永远看不到“连接泄漏”的瞬时峰值,只能靠 wait_count 和 in_use_connections 的长期趋势判断。需要泄漏检测,得自己包装 sql.Conn 或用 database/sql 原生 hook。


















