必须配对设置SetConnMaxIdleTime和SetConnMaxLifetime,前者控制空闲连接回收时机(如45秒),后者确保连接在服务端wait_timeout前退役(如240秒),避免复用失效连接导致invalid connection或i/o timeout。

连接池超时不是“调大 timeout 就完事”的问题,它本质是 Go database/sql 连接池与 MySQL(或其他 DB)服务端配置不协同导致的 invalid connection、i/o timeout 等错误反复出现。
为什么 SetConnMaxIdleTime 必须显式设置且不能乱设
Go 1.15+ 才支持 SetConnMaxIdleTime,它控制空闲连接在池中存活多久。不设或设错会直接导致大量 invalid connection 报错:
- MySQL 默认
wait_timeout = 60s,如果你的SetConnMaxIdleTime设为2 * time.Hour,连接在 DB 端早被干掉,Go 端复用时就炸 - 只设
SetConnMaxIdleTime却没设SetMaxIdleConns,连接用完即销毁,根本不会进空闲队列,该参数完全不生效 - 设得太短(如
5s)会导致高频建连,压测时 CPU 和 TCP TIME_WAIT 暴增
推荐组合:SetMaxIdleConns(20) + SetConnMaxIdleTime(45 * time.Second)(略小于 DB 的 wait_timeout)
invalid connection 错误怎么精准识别和恢复
这个错误几乎全是服务端主动断连后客户端无感知复用导致的,不能靠 err != nil 泛判,必须做类型断言:
- MySQL 场景:用
errors.As(err, &myErr)断言为*mysql.MySQLError,检查myErr.Number() == 2003 || myErr.Number() == 2013 - PostgreSQL 场景:断言为
*pq.Error,检查pgErr.Code[0:2] == "08"(如"08006") - 兜底判断:错误字符串含
"invalid connection"、"i/o timeout"、"connection refused"也应归入连接层故障
识别后不要 gorm.Open 重来——那会泄漏连接池。正确做法是调用 db.DB().Ping() 主动探活,失败则记录告警并等待连接池自动重建空闲连接。
事务里遇到连接中断,重试前必须做三件事
GORM 不自动重试事务,但业务层加重试时,稍有不慎就会卡死或数据错乱:
- 每次重试前必须调用
tx.Rollback(),否则连接状态残留,下次Begin()可能 hang 住 - 只对可恢复错误重试:如
driver.ErrBadConn、MySQL 错误码1205(死锁)、2013(连接丢失),别对UNIQUE KEY冲突重试 - 必须带指数退避和 context 控制生命周期,否则网络抖动期间可能触发雪崩式重试
典型写法是封装一个 RetryTransaction 函数,内部用 for i := 0; i + time.Sleep(time.Second 。
DSN 里的 timeout 参数跟连接池超时完全无关
很多人把 DSN 中的 timeout=10s 或 readTimeout=30s 当成连接池空闲超时,这是根本性误解:
-
timeout只控制新建连接阶段的握手耗时,连上了就失效 -
readTimeout/writeTimeout是 MySQL 驱动层参数,影响单次查询/写入的 I/O 超时,不影响连接是否该回收 - 真正管“连接空闲多久该关”的只有
SetConnMaxIdleTime,且必须通过db.DB()获取底层*sql.DB后设置
最容易被忽略的一点:所有连接池参数(SetMaxOpenConns、SetMaxIdleConns、SetConnMaxIdleTime、SetConnMaxLifetime)都必须在 gorm.Open 之后、首次 Ping() 之前完成设置——晚了部分连接可能已进入空闲队列,参数对其无效。











