必须手动配置GORM底层*sql.DB的连接池参数:先设SetMaxOpenConns(如40),再设≤它的SetMaxIdleConns(如20),并设SetConnMaxLifetime(如240s);启动时全局初始化,禁用handler内重复Open。

不配连接池参数,Gin 应用在高并发下大概率直接崩在 MySQL 的 Too many connections 上——这不是 Gin 的问题,而是你没动 *sql.DB 的默认值。
为什么 GORM 初始化后还要手动调 db.DB().SetMaxOpenConns()
GORM 的 gorm.Open() 返回的是 *gorm.DB,它只是个封装层,底层共享一个 *sql.DB 实例;而连接池行为完全由这个原生句柄控制。框架本身不接管、不增强、也不默认设合理值。
- 默认
MaxOpenConns = 0:意味着无上限,MySQL 的max_connections被打满就报错 - 默认
MaxIdleConns = 2:太小,高并发时频繁新建/销毁连接,增加延迟和 fd 压力 - 若只初始化 GORM、不调
db.DB()拿原生句柄设参,等于没配池
SetMaxIdleConns 设高了会 panic,不是警告
Go 1.12+ 对空闲连接数做了硬校验:如果 SetMaxIdleConns(n) 大于当前 MaxOpenConns 值,运行时直接崩溃,错误信息是 "max idle conns exceeds max open conns"。
- 典型误配:
db.SetMaxOpenConns(5)后又写db.SetMaxIdleConns(10)→ 立即 crash - 正确顺序:先设
MaxOpenConns,再设 ≤ 它的MaxIdleConns - 生产建议值:
MaxIdleConns = MaxOpenConns × 0.5(比如开 100 连接,空闲留 50)
SetConnMaxLifetime 不是保活,是“到期就丢”
db.Ping() 只验证此刻能建连并执行 SELECT 1,它不检查池里已有空闲连接是否还活着。MySQL 的 wait_timeout=300 或 RDS Proxy 主动断连后,坏连接仍躺在池里,直到下次被取出执行 Query() 才暴露 i/o timeout 或 invalid connection。
-
SetConnMaxLifetime是唯一能缓解该问题的机制:它按时间强制关闭连接,不等 DB 来杀 - 推荐设为比数据库
wait_timeout小 5~10 分钟,例如 MySQL 设了 300s,则 Go 侧设240 * time.Second - 注意:它不发心跳、不检测连通性,纯计时器驱动,关完就重建新连接
Gin 启动时必须全局初始化一次 *sql.DB,别在 handler 里 sql.Open
把 sql.Open() 或 gorm.Open() 写在 Gin 的路由 handler 里,等于每次请求都新建一个连接池——连接泄漏、文件描述符耗尽、database is closed 频发。
- 正确姿势:应用启动时(如
bootstrap/database.go)初始化一次*gorm.DB,注入到 service 或 handler 层复用 - 确保配置项来自 Viper/TOML 等外部源,避免硬编码 DSN
- 别漏掉
SetConnMaxLifetime和SetMaxOpenConns,这两个不设,其他都白搭
最常被忽略的一点:连接池参数不是“设了就稳”,它得和你的 QPS、平均查询耗时、MySQL wait_timeout 匹配。比如峰值 QPS 是 200、平均查询 150ms,按公式 (150 × 200) / 1000 + 20% ≈ 36,那 MaxOpenConns 设 40 比设 100 更合理——大了反而加剧争抢和超时。


















