db.close()不能直接defer调用,必须配合http.server.shutdown()在所有请求结束后安全关闭;需先shutdown等待连接归还,再close,并确保rows.close()已显式调用。

主动关闭数据库连接不是调 db.Close() 就完事——它只释放连接池,不等正在用的连接归还,可能引发 panic 或数据写入丢失。
为什么 db.Close() 不能直接放在 defer 里?
常见错误是把 db.Close() 写在 main() 开头的 defer 里,比如:
func main() {
db, _ := sql.Open("mysql", dsn)
defer db.Close() // ❌ 错!服务还没启动就关了
r := gin.Default()
r.Run(":8080")
}
这样会导致 router 还没跑起来,db 就被提前关闭。后续任何数据库操作都会返回 "sql: database is closed" 错误。
正确关闭时机:必须配合 http.Server.Shutdown()
数据库连接池应和 HTTP server 生命周期对齐:等所有请求结束、连接归还后,再关 db。
-
http.Server.Shutdown()会等待活跃连接完成(或超时),此时才是安全关db的窗口 - 必须在
Shutdown()返回后再调db.Close(),否则可能仍有 goroutine 在用连接 - 别忘了
db.Ping()检查连接有效性,避免 shutdown 前就已断连却无感知
完整 shutdown 流程示例
以下是最小可行闭环(含信号监听 + 超时控制 + 数据库清理):
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<p>go func() {
</p><p></p><p></p><p></p><p>注意:<code>db.Close()</code> 返回的是“连接池已关闭”,不代表所有底层 TCP 连接立刻断开——它只是拒绝新借连接,并等已有连接主动归还。如果业务中有长事务或未关闭的 <code>rows</code>,得确保它们先完成或显式 <code>rows.Close()</code>。</p><h3>容易被忽略的坑:未关闭的 <code>rows</code> 会卡住 <code>db.Close()</code>
</h3><p><code>sql.Rows</code> 不是自动关闭的,必须显式调 <code>rows.Close()</code>,否则 <code>db.Close()</code> 会一直阻塞(直到超时)。</p>
- 常见于
SELECT查询后忘记defer rows.Close() - 如果用了
for rows.Next()但循环提前break或return,rows.Close()就不会执行 - 建议统一用
defer rows.Close()放在rows, err := db.Query(...)后紧邻行
真正麻烦的不是关不掉,而是关得太晚——它拖慢整个 shutdown 过程,让发布卡在最后一步。











