Golang Fiber API首次请求后数据库连接关闭问题求助
问题排查:首次请求正常,后续请求返回“sql: database is closed”
问题描述
使用Golang + Fiber框架开发API时,首次请求可正常响应,但后续所有请求均返回错误:sql: database is closed。通过Fiber Context传递数据库连接池实例(*sqlx.DB),相关代码及请求响应如下:
main.go
func main() { app := fiber.New() db, err := sqlx.Connect("pgx", os.Getenv("POSTGRESQL_URL")) if err != nil { panic(err) } if err = db.Ping(); err != nil { panic(err) } db.SetMaxOpenConns(10) db.SetMaxIdleConns(5) db.SetConnMaxLifetime(5 * time.Minute) db.SetConnMaxIdleTime(5 * time.Minute) defer db.Close() configure_router.ConfigureRouter(app, db) if err = app.Listen(os.Getenv("PORT")); err != nil { log.Fatalln(err) } }
configure_router.go
func ConfigureRouter(app *fiber.App, db *sqlx.DB) { //Middlewares app.Use(logger.New(logger.Config{ Format: "[${ip}]:${port} ${time} ${status} - ${method} ${path}\n", })) app.Use(cors.New(cors.Config{ //AllowOrigins: "http://localhost:3000", AllowHeaders: "Origin, Content-Type, Accept", })) app.Use("/api", func(ctx *fiber.Ctx) error { ctx.Context().SetUserValue("dbConn", db) return ctx.Next() }) //Authentication endpoints app.Post("api/register", register.Register) app.Post("api/auth/login", login.Login) }
register.go
func Register(ctx *fiber.Ctx) error { conn := ctx.Context().UserValue("dbConn").(*sqlx.DB) var in In if err := ctx.BodyParser(&in); err != nil { return make_response.MakeInfoResponse(ctx, fiber.StatusUnprocessableEntity, 1, err.Error()) } if in.Email == "" || in.Password == "" { return make_response.MakeInfoResponse(ctx, fiber.StatusBadRequest, 1, "Incorrect data input") } elementExist := false err := conn.Get(&elementExist, "select exists(select email from users where email = $1)", in.Email) // Here programm fall in second request if err != nil { return make_response.MakeInfoResponse(ctx,fiber.StatusInternalServerError, 1, err.Error()) } if elementExist { return make_response.MakeInfoResponse(ctx, fiber.StatusBadRequest, 1, "User already registered!") } passwordHash, err := hash_passwords.HashPassword(in.Password) if err != nil { return err } _, err = conn.Exec("insert into users (email, password_hash) values ($1, $2)", in.Email, passwordHash) if err != nil { return make_response.MakeInfoResponse(ctx, fiber.StatusInternalServerError, 1, err.Error()) } return make_response.MakeInfoResponse(ctx, fiber.StatusOK, 0, "Registration was successful!") }
请求示例
{ "email": "test@gmail.com", "password": "123123123" }
首次请求响应
{ "error_code": 0, "message": "User already registered!" }
后续请求响应
{ "error_code": 1, "message": "sql: database is closed", }
根因分析
核心问题在于main函数中通过defer db.Close()提前关闭了数据库连接池:
defer语句会在所在函数(main)退出时执行,虽然app.Listen()是阻塞调用,但如果服务启动后意外触发main函数退出(如未捕获的信号、隐性panic),会直接触发db.Close()关闭连接池- 错误信息
sql: database is closed明确表明:当前使用的*sqlx.DB实例已被主动关闭,所有后续基于该实例的数据库操作都会失败
另外需排查是否有其他未贴出的代码(如login handler)意外调用了db.Close()。
解决方案
- 移除main函数中的
defer db.Close(),改用优雅关闭机制,在服务收到终止信号时再关闭数据库连接池 - 检查所有handler代码,确保没有意外调用
db.Close()(连接池应全局复用,无需在单个请求中关闭)
修正后的代码示例
main.go
package main import ( "log" "os" "syscall" "time" "os/signal" "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" _ "github.com/jackc/pgx/v5/stdlib" ) func main() { app := fiber.New() db, err := sqlx.Connect("pgx", os.Getenv("POSTGRESQL_URL")) if err != nil { panic(err) } if err = db.Ping(); err != nil { panic(err) } // 配置连接池参数 db.SetMaxOpenConns(10) db.SetMaxIdleConns(5) db.SetConnMaxLifetime(5 * time.Minute) db.SetConnMaxIdleTime(5 * time.Minute) configure_router.ConfigureRouter(app, db) // 优雅关闭:监听终止信号 sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) go func() { <-sigChan log.Println("开始优雅关闭服务...") // 关闭Fiber服务 if err := app.Shutdown(); err != nil { log.Fatalf("服务关闭失败: %v", err) } // 关闭数据库连接池 if err := db.Close(); err != nil { log.Fatalf("数据库连接关闭失败: %v", err) } log.Println("服务已优雅关闭") }() // 启动服务 if err = app.Listen(os.Getenv("PORT")); err != nil { log.Fatalln(err) } }
其他注意事项
*sqlx.DB是线程安全的连接池,全局复用即可,无需在每个请求中创建或关闭连接- 若手动从连接池获取单个连接(如
conn, err := db.Conn()),使用后需调用conn.Close()归还连接,但无需关闭整个连接池
内容的提问来源于stack exchange,提问作者Golov Peter
相关产品推荐
相关产品推荐

