数据库连接池必须在init()中初始化,因init()只执行一次且早于控制器实例化;若在控制器中初始化会导致重复创建、资源泄漏和“too many open files”错误。

数据库连接池必须在 init() 里初始化,不能放控制器里
Go 的 init() 函数只执行一次,且早于任何控制器实例化,这是唯一安全放置全局连接池的地方。如果把 gorm.Open() 或 sql.DB 配置逻辑写进控制器的 BeforeActivation 或构造函数里,每次请求都会尝试新建连接池,导致资源泄漏和“too many open files”错误。
常见错误现象:服务跑几小时后突然 500,日志里反复出现 dial tcp: lookup xxx: no such host 或 connection refused——这往往不是网络问题,而是连接池没复用、旧连接堆积后被系统回收,新请求又撞上失效连接。
-
sql.DB实例本身是线程安全的,应作为全局变量(如var db *gorm.DB)声明在包级作用域 - 调用
db.DB()获取底层*sql.DB后,立刻设置SetMaxIdleConns、SetMaxOpenConns、SetConnMaxLifetime - 不要在控制器方法里调用
db.Close()—— 这会关掉整个池,后续所有请求都失败
SetMaxOpenConns 和 SetMaxIdleConns 的典型配比
这两个值不是越大越好。Iris 默认不干预连接池行为,全靠你手动调优。生产环境常见误配是把 SetMaxOpenConns 设成 1000,结果数据库扛不住并发连接数,直接拒绝新连接。
推荐起始值(MySQL 场景):
-
SetMaxOpenConns(30):匹配常见 MySQL 默认max_connections=151,留出余量给其他服务或后台任务 -
SetMaxIdleConns(10):避免空闲连接长期占用,又不至于每次请求都重建连接 -
SetConnMaxLifetime(1h):强制一小时换一批连接,防止因数据库侧连接超时(如 AWS RDS 的wait_timeout=300)导致的 stale connection
注意:SetConnMaxLifetime 必须小于数据库服务端的连接空闲超时时间,否则你会看到大量 invalid connection 错误。
MVC 控制器里怎么安全用 DB?别传 *gorm.DB,传封装好的 Repository
直接在控制器结构体字段里塞 db *gorm.DB 看似简单,但会导致测试困难、事务难控制、以及意外共享连接状态。Iris 的 mvc.Application.Register() 支持注入任意类型,应该用它传一层抽象。
正确做法是定义一个接口 + 实现:
type UserRepository interface {
FindByID(id int64) (*User, error)
}
type GORMUserRepository struct {
db *gorm.DB
}
func (r *GORMUserRepository) FindByID(id int64) (*User, error) {
var u User
return &u, r.db.First(&u, id).Error
}
然后在 main.go 里注册:
repo := &GORMUserRepository{db: GetDB()}
mvc.New(app.Party("/api")).Register(repo).Handle(new(UserController))
控制器里就只依赖接口:
type UserController struct {
Repo UserRepository
}
func (c *UserController) Get(ctx iris.Context) {
u, _ := c.Repo.FindByID(1)
ctx.JSON(u)
}
这样既解耦,又方便单元测试 mock 掉 DB 层。
事务不能跨请求,控制器里别自己 Begin() / Commit()
Iris 的 MVC 控制器生命周期 = 单次 HTTP 请求。如果你在 PostCreate 方法开头调 db.Begin(),结尾 Commit(),看似合理,但一旦中间有 panic 或中间件提前终止响应,事务就卡住,连接不会释放,最终耗尽池子。
更可靠的方式是把事务控制权交给 service 层,并确保它在 defer 中兜底:
func (s *UserService) Create(tx *gorm.DB, u *User) error {
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Create(u).Error; err != nil {
return tx.Rollback().Error
}
return tx.Commit().Error
}
或者直接用 GORM 的 Transaction 方法,它内部已处理 panic 恢复:
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&u).Error; err != nil {
return err
}
return tx.Create(&log).Error
})
这个模式下,控制器只管传参、收结果,不碰事务边界。


















