一眼看出是连接池堵了而非sql慢:inuse接近maxopenconnections、waitcount>0且waitduration持续增长、idle==0但inuse不回落,三者同时出现即表明请求卡在“等连接”阶段,sql尚未执行。

怎么一眼看出是连接池堵了而不是 SQL 慢
执行 db.Stats() 后重点关注三个字段:InUse 接近或等于 MaxOpenConnections、WaitCount > 0 且 WaitDuration 持续增长、Idle == 0 但 InUse 没回落。这三点同时出现,基本可以断定不是 SQL 慢,而是连接在排队等释放。
常见错误现象:
-
context deadline exceeded频繁出现,但 MySQL 的SHOW PROCESSLIST里没看到长事务 - 接口偶发超时,重启服务后立刻恢复,过几小时又复现
- Prometheus 中
sql_db_wait_count_total持续爬升,而sql_db_open_connections卡在上限
真正该查的不是慢日志,而是连接池状态——WaitCount 非零就说明请求卡在“拿连接”这一步,SQL 根本还没执行。
事务里忘了 Commit/Rollback 是最隐蔽的连接杀手
事务开启后,连接被绑定在 *sql.Tx 上,直到显式调用 Commit() 或 Rollback() 才归还。漏掉任何一个,连接就永远卡住。
典型场景:
-
if err != nil { return }直接返回,没写tx.Rollback() -
defer tx.Commit()被 panic 绕过,实际没执行 - 事务内调用了外部 HTTP 服务,超时后没兜底 rollback
安全写法是用匿名 defer 函数检查状态:
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// ...业务逻辑
if err := tx.Commit(); err != nil {
tx.Rollback()
return err
}
SetMaxIdleConns 设了还是频繁建连?原因在这儿
SetMaxIdleConns 控制的是“用完归还后能留几个不关”,但它不会主动预热连接。空闲连接快速归零,往往不是参数设小了,而是以下情况在起作用:
- 没设
SetConnMaxLifetime:DB 主动断连后,Go 不知道连接失效,下次复用时报i/o timeout,然后丢弃并新建 -
SetMaxIdleConns设为 1:刚归还,下一个请求立刻拿走,根本攒不出空闲数 - 连接老化被后台 goroutine 清理:所有空闲连接都超了
ConnMaxLifetime,池子真空
建议组合配置:SetMaxIdleConns(10) + SetConnMaxLifetime(30 * time.Minute)。注意:Go 1.15+ 才支持 SetConnMaxIdleTime,它才是真正控制空闲连接存活时长的参数,别和 ConnMaxLifetime 混用。
为什么 db.QueryRow 慢但 WaitCount 为 0
如果 WaitCount == 0,但查询延迟高、P99 拉得长,那问题大概率不在连接池,而在 SQL 本身或网络链路。
这时该看:
- MySQL 的慢查询日志是否记录了该语句
-
EXPLAIN输出中type是否为ALL,rows是否远超实际匹配行数 -
SHOW PROCESSLIST里该连接状态是不是Sending data或Locked - DSN 里的
timeout=300ms参数只管建连阶段,对查询无用;必须用QueryRowContext+context.WithTimeout
一个容易被忽略的点:sql.ErrNoRows 是正常错误,但如果它高频出现且伴随高延迟,往往说明查询没加 LIMIT 或缺失索引,导致扫描大量无效行,间接拖长连接占用时间。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











