fiber 不管理数据库事务,因它仅是 http 路由层,不介入事务生命周期;事务须手动开启、提交或回滚,且需绑定请求上下文、覆盖所有退出路径(含 panic),gorm 等操作必须使用事务对象而非原 db 实例。

为什么 Fiber 本身不管理数据库事务
Fiber 是 HTTP 路由层框架,不介入数据库连接池、事务生命周期或 ACID 控制——它只负责把 c.Context() 传下去。事务必须由你显式开启、提交或回滚,Fiber 不会自动 wrap handler、也不提供 WithTransaction 中间件。常见错误是以为加个中间件就能“全局事务”,结果 panic 或漏 rollback。
在 handler 里手动控制事务的正确姿势
核心原则:事务对象(如 *sql.Tx 或 *gorm.DB)必须绑定到当前请求上下文,且 rollback 必须覆盖所有 exit 路径(包括 panic、error return、提前 return)。
- 用
defer+recover()捕获 panic 并 rollback,否则连接可能卡住 - 不要在事务外调用
db.WithContext(c.Context())后直接Create()——那只是带超时的普通查询,不是事务内操作 - GORM 用户注意:
tx := db.Begin()后,所有操作必须用tx.Create()、tx.First(),不能混用db实例 - 示例关键片段:
app.Post("/order", func(c *fiber.Ctx) error {
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
}
}()
if tx.Error != nil {
return c.Status(500).SendString("tx start failed")
}
defer func() {
if c.Response().StatusCode() >= 400 || c.Response().StatusCode() == 0 {
tx.Rollback()
return
}
tx.Commit()
}()
// 实际业务逻辑,全部走 tx.XXX()
if err := tx.Create(&order).Error; err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(order)
})
用中间件传递事务对象(需谨慎)
可以写一个中间件把 *sql.Tx 注入 c.Locals,但仅限单数据库、单事务场景;多库或多阶段事务(如先写 DB 再发 MQ)不适用,且容易和 context 超时错配。
- 中间件里不能直接
db.Begin()—— 必须等 handler 开始执行后再开,否则超时未用就浪费 - 推荐只在 handler 入口处开事务,避免中间件里隐式依赖
- 若坚持用中间件,务必检查
c.Locals("tx") == nil,防止重复开启 - 别把
tx.Commit()放在中间件defer里——handler 可能早于中间件结束
事务与优雅停机的协同要点
当收到 SIGTERM 时,http.Server.Shutdown() 会等待 handler 完成,但不会等你正在执行的 tx.Commit()。如果事务耗时长(比如批量插入),它可能被强制中断。
- 给事务设置独立超时:
ctx, cancel := context.WithTimeout(c.Context(), 10*time.Second),再传给db.WithContext(ctx) - 在
Shutdown()前,可加一个sync.WaitGroup计数活跃事务,但注意 wg.Add(1) 必须在事务真正开始后(即Begin()成功后)调用 - 最易忽略的是:GORM 的
tx.Commit()本身可能阻塞(如网络抖动),必须用带超时的 context 包裹,而不是依赖外部 Shutdown 超时











