Kratos服务连接池泄露主因是*sql.DB生命周期失控:未用context超时、rows.Close()遗漏、手动conn未关闭等;需显式配置SetMaxOpenConns/SetMaxIdleConns,结合Stats()监控与pprof定位。

Kratos 框架本身不内置连接池泄露检测机制,但泄露问题 90% 出现在 database/sql 底层连接管理失控——不是 Kratos 的锅,而是你没管好 *sql.DB 实例的生命周期和调用路径。
为什么 Kratos 服务容易出现连接池泄露
Kratos 默认鼓励复用全局 *sql.DB 实例(比如通过 wire 注入),这本是正确做法;但一旦在中间件、拦截器或业务 handler 中发生以下任一情况,连接就会“借出不还”:
- 未使用
context.Context传递超时控制,导致查询卡住、连接长期被占用 - 异常分支中跳过了
rows.Close()或tx.Rollback(),尤其在defer外提前 return - 手动调用
db.Conn(ctx)获取底层*sql.Conn后,忘记调用conn.Close() - 协程中误用同一个
*sql.DB执行阻塞操作(如大结果集遍历未加 context 控制),拖慢连接归还
查 waiting=Z:用 Prometheus + Kratos 自带指标定位池压
Kratos 集成 go-sql-driver/mysql 或 pgx 时,若启用 database/sql 的 Stats() 并暴露为 Prometheus 指标(如 sql_open_connections、sql_idle_connections),就能直接看出是否“只借不还”:
- 持续观察
sql_idle_connections趋近于 0,而sql_open_connections接近maxOpen→ 典型泄露前兆 - 搭配
sql_wait_duration_seconds_count突增 → 说明大量 goroutine 在等空闲连接,已开始排队 - 注意:Kratos 默认不自动采集这些指标,需在初始化
*sql.DB后显式注册:prometheus.MustRegister(sqlstats.NewStatsCollector(db, "user_db"))
抓泄漏源头:用 pprof + runtime.Stack 锁定未关闭的 rows/txt
连接泄露本质是 *sql.Rows 或 *sql.Tx 对象未释放,它们背后持有着 *sql.conn。最有效的现场排查方式是结合运行时堆栈:
- 启动服务时加
-gcflags="-l"禁用内联,让函数名可追踪 - 访问
/debug/pprof/goroutine?debug=2,搜索关键词QueryContext、QueryRowContext、BeginTx—— 找出哪些 goroutine 卡在数据库调用上且未结束 - 在可疑 handler 中加日志:
log.Printf("before query, idle=%d", db.Stats().Idle)和log.Printf("after rows.Close, idle=%d", db.Stats().Idle),确认是否真归还 - 特别注意:用
rows.Next()遍历时,必须保证rows.Close()在所有分支(包括 break/return/error)后执行,推荐写成defer rows.Close()放在rows, err := db.QueryContext(...)之后立即调用
防泄漏的硬性配置与代码习惯
再好的排查也比不上一开始就堵死漏洞。Kratos 项目里这几条必须写进团队规范:
-
db.SetMaxOpenConns(20)和db.SetMaxIdleConns(10)必须显式设置,不能依赖默认值(默认 MaxOpen=0 表示无限制,极易打爆 DB) - 所有
QueryContext/ExecContext必须传入带 deadline 的ctx,例如ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) - 禁止在 defer 中调用
tx.Commit()或tx.Rollback()—— 应该用if err != nil { tx.Rollback() }显式控制,否则 panic 时 rollback 可能不执行 - 避免在 handler 中直接 new
*sql.DB;统一由 wire 构建并注入,确保复用与可测性
真正难排查的从来不是“哪里漏了”,而是“漏了之后还跑得动”——连接池泄露初期表现温和,直到某次流量尖峰或慢查询批量触发,idle 连接耗尽,整个服务才突然雪崩。盯紧 db.Stats() 的三个数字:Open、Idle、InUse,比任何日志都早一步预警。











