beego ORM 默认不启用连接池,必须显式配置 maxIdleConns 和 maxOpenConns;需根据 MySQL max_connections 设置合理值,并手动调用 SetConnMaxLifetime 和 SetConnMaxIdleTime;事务和 rows 必须正确关闭,否则导致连接泄漏。

beego ORM 默认不启用连接池,必须显式配置
beego 的 orm.RegisterDataBase 在不传第 4、5 个参数时,maxIdleConns 和 maxOpenConns 均为 0 —— 这意味着**完全不启用连接复用**,每次查询都新建 TCP 连接。线上高并发下必然触发 ERROR 1040 (HY000): Too many connections 或大量 connection reset by peer。
必须在注册数据库时传入非零值:
orm.RegisterDataBase("default", "mysql", "user:pass@/db?charset=utf8", 20, 50)
// 第4个参数:maxIdleConns = 20
// 第5个参数:maxOpenConns = 50
- 这两个值不能凭空设;先查数据库真实
max_connections(MySQL 执行SHOW VARIABLES LIKE 'max_connections'),再按 60%~80% 反推 - 若数据库
max_connections=151,单台应用服务器建议设maxOpenConns=50,留余量给 DBA 操作和突发流量 -
maxIdleConns推荐设为maxOpenConns的 1/2~2/3(如 50 → 20~30),避免空闲连接长期占着但又不释放
SetConnMaxLifetime 必须手动设置,beego ORM 不提供直接接口
beego ORM 封装了 *sql.DB,但没暴露 SetConnMaxLifetime 方法。若不设,连接可能被 MySQL 的 wait_timeout(常被调为 300 秒)或中间件(如 ALB、NAT 网关)静默断开,后续复用时抛 invalid connection 或 broken pipe。
需绕过 ORM 获取底层 *sql.DB 对象后设置:
db, err := orm.GetDB("default")
if err != nil {
log.Fatal(err)
}
db.SetConnMaxLifetime(4 * time.Minute) // 比数据库 wait_timeout 小 1 分钟
db.SetConnMaxIdleTime(5 * time.Minute) // Go 1.15+ 必加,防防火墙/NAT 断连
-
SetConnMaxLifetime不是“空闲超时”,它控制连接从创建起最多活多久;必须略小于数据库侧的超时(如 MySQLwait_timeout=300→ 设4*time.Minute) -
SetConnMaxIdleTime是 Go 1.15+ 新增,专治空闲连接被中间设备丢弃的问题;旧版本只能靠SetMaxIdleConns+ 主动回收模拟 - 这两个方法必须在
orm.RegisterDataBase之后、首次查询之前调用,否则部分连接已创建却未受控
事务泄漏比连接泄漏更隐蔽,且必导致 OpenConnections 持续上涨
beego ORM 的 orm.Begin() 返回的是 *sql.Tx,它会独占一个连接直到 Commit() 或 Rollback()。漏掉任一调用,该连接就永远卡住,db.Stats().OpenConnections 持续增长,WaitCount 开始飙升。
正确写法必须含三要素:
o := orm.NewOrm()
tx, err := o.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
}
- 不能只靠
defer tx.Rollback()—— 如果事务成功提交,Rollback()会报sql: transaction has already been committed or rolled back,但连接已归还;问题在于没覆盖 panic 场景 - 所有
tx.Query()/tx.Exec()必须走tx对象,混用o.Raw().Exec()或o.QueryTable().Filter()会绕过事务,造成数据不一致 - 高频小操作(如循环插入)应改用
InsertMulti+ 单事务,而非每个循环都Begin/Commit
rows.Close() 是强制项,不是可选项
beego ORM 的 o.Raw().QueryRows() 或原生 db.Query() 返回的 *sql.Rows,若不显式 .Close(),底层连接不会归还池中 —— 这是比事务泄漏更难排查的泄漏点,因为日志里通常不报错,只表现为响应变慢、连接数缓慢爬升。
常见错误写法:
// ❌ 错误:没 Close,连接永远卡住
rows, _ := o.Raw("SELECT id FROM user").QueryRows()
for rows.Next() {
// ...
}
// rows.Close() 缺失!
正确写法:
// ✅ 正确:显式 Close,且 defer 放在 rows 创建后立刻
rows, err := o.Raw("SELECT id FROM user").QueryRows()
if err != nil {
return err
}
defer rows.Close() // 必须在这里 defer,不能等 for 结束
for rows.Next() {
// ...
}
-
defer rows.Close()必须紧跟在QueryRows()后,否则rows为 nil 时 defer 会 panic - 即使
rows.Next()一次都没进(空结果集),也必须Close(),否则连接不释放 - ORM 的
o.QueryTable().All()等高级方法内部已自动 Close,但只要用了Raw或原生db.Query,就必须自己管
实际部署时最容易被忽略的,是把 SetConnMaxLifetime 和 SetConnMaxIdleTime 设成相同值 —— 它们作用对象完全不同:前者针对“连接从创建起存活多久”,后者针对“连接空闲多久后回收”。混用会导致空闲连接提前被杀,或活跃连接在中间设备断连后仍被复用。


















