You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()。

解决方案

  1. 移除main函数中的defer db.Close(),改用优雅关闭机制,在服务收到终止信号时再关闭数据库连接池
  2. 检查所有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 15:07:02