正确做法是初始化一次*gorm.db、复用连接池,并通过中间件或依赖注入传入handler;全局声明易致并发关闭、测试困难、多源混乱,且fiber.ctx无原生context注入。

直接说结论:别把 *gorm.DB 当全局变量塞进 fiber.App,也别每次请求都调用 gorm.Open() —— 正确做法是初始化一次、复用连接池、通过中间件或依赖注入把 *gorm.DB 传入 handler。
为什么不能全局声明 DB *gorm.DB?
看似方便,实则埋雷:
- 并发下
DB.Close()可能被意外调用,导致后续请求报"invalid connection" - 测试时无法独立替换 mock DB,单元测试写不下去
- 多数据源场景下,全局变量命名和切换逻辑混乱(比如
MasterDB/SlaveDB) - Fiber 的
fiber.Ctx不自带 context value 注入机制,硬塞全局变量会让 handler 依赖隐式状态
gorm.Open() 必须配连接池参数
默认的 *sql.DB 连接池极弱(MaxIdleConns=2, MaxOpenConns=0),高并发下会卡死或抛 "dial tcp: i/o timeout"。初始化时必须显式设置:
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10) // 避免频繁建连
sqlDB.SetMaxOpenConns(50) // 根据压测调整,别设太高
sqlDB.SetConnMaxLifetime(1h) // 防止数据库主动断连后连接僵死
注意:SetConnMaxLifetime 单位是 time.Duration,不是字符串;MySQL 默认 wait_timeout 是 8 小时,设成 1 小时更安全。
怎么把 *gorm.DB 安全传给 Fiber handler?
推荐两种方式,按项目规模选:
- 小项目:用
fiber.App.Use()中间件挂载到c.Locals(轻量、无侵入) - 中大项目:用结构体封装 app 实例,把
*gorm.DB作为字段注入(利于测试、扩展)
示例(中间件方式):
func DBMiddleware(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
c.Locals("db", db)
return c.Next()
}
}
app := fiber.New()
app.Use(DBMiddleware(db))
app.Get("/users", func(c *fiber.Ctx) error {
db := c.Locals("db").(*gorm.DB) // 类型断言要小心
var users []User
if err := db.Find(&users).Error; err != nil {
return c.Status(500).SendString(err.Error())
}
return c.JSON(users)
})
⚠️ 注意:如果用了多个 ORM 实例(如读写分离),别共用一个 key 名,比如用 "db_master" 和 "db_slave" 区分。
预加载与事务在 Fiber 中怎么写才不翻车?
Fiber 没有内置事务生命周期管理,必须手动控制 scope 和 rollback 条件:
- 不要在 middleware 里开事务然后跨 handler 提交 —— 请求中断时没法回滚
- 事务逻辑应尽量收口在单个 handler 或 service 方法内
- 预加载慎用
Joins:它生成 LEFT JOIN,关联数据为空时主记录可能被过滤掉;Preload更安全但 N+1 问题需靠Limit+ 分页缓解
示例(带错误处理的事务):
app.Post("/orders", func(c *fiber.Ctx) error {
db := c.Locals("db").(*gorm.DB)
return db.Transaction(func(tx *gorm.DB) error {
var order Order
if err := c.BodyParser(&order); err != nil {
return err
}
if err := tx.Create(&order).Error; err != nil {
return err
}
// 关联插入
for _, item := range order.Items {
item.OrderID = order.ID
if err := tx.Create(&item).Error; err != nil {
return err
}
}
return nil
})
})
真正容易被忽略的是:Fiber handler 返回 error 不会自动触发事务 rollback —— 必须由 Transaction 函数内部显式返回 error 才生效。handler 外层的 error 被 Fiber 捕获后只影响 HTTP 响应,跟事务无关。











