
在 Go 中使用 database/sql 时,若在初始化函数中调用 defer db.Close(),会导致连接在函数返回前即被关闭,造成后续操作失败(如 sql: database is closed)。正确做法是将 defer db.Close() 移至主调用作用域(如 main 函数)中,确保连接在程序逻辑结束后才释放。
在 go 中使用 `database/sql` 时,若在初始化函数中调用 `defer db.close()`,会导致连接在函数返回前即被关闭,造成后续操作失败(如 `sql: database is closed`)。正确做法是将 `defer db.close()` 移至主调用作用域(如 `main` 函数)中,确保连接在程序逻辑结束后才释放。
问题核心在于对 defer 执行时机的误解:defer 语句在包含它的函数即将返回时执行,而非在变量作用域结束时。在原代码中,defer db.Close() 位于 Create() 函数内,因此当 Create() 执行完毕并返回 *sql.DB 时,db.Close() 立即被触发——此时数据库连接已被关闭,而 main() 中拿到的只是一个已失效的句柄。
以下是修复后的完整示例:
package main
import (
"database/sql"
_ "github.com/lib/pq"
"fmt"
"log"
)
const (
HOST = "HOSTNAME"
USER = "USER"
PASSWORD = "PASSWORD"
DATABASE = "DB"
)
func Create() (*sql.DB, error) {
dbinfo := fmt.Sprintf("host=%s user=%s password=%s dbname=%s sslmode=disable",
HOST, USER, PASSWORD, DATABASE)
db, err := sql.Open("postgres", dbinfo)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
// 验证连接可用性
if err = db.Ping(); err != nil {
db.Close() // 显式关闭,避免资源泄漏
return nil, fmt.Errorf("failed to ping database: %w", err)
}
return db, nil // 不在此处 defer!
}
func main() {
db, err := Create()
if err != nil {
log.Fatal(err)
}
defer db.Close() // ✅ 正确位置:确保整个 main 流程结束后再关闭
// 设置连接池参数(推荐)
db.SetMaxOpenConns(20)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(30 * time.Minute)
querStmt, err := db.Prepare("SELECT COUNT(*) FROM table")
if err != nil {
log.Fatalf("Cannot prepare query: %v", err)
}
defer querStmt.Close() // 建议显式关闭 Stmt
rows, err := querStmt.Query()
if err != nil {
log.Fatalf("Cannot execute query: %v", err)
}
defer rows.Close()
var count int
if rows.Next() {
if err := rows.Scan(&count); err != nil {
log.Fatal(err)
}
}
fmt.Printf("Row count: %d\n", count)
}关键修正点:
- Create() 函数不再包含 defer db.Close(),仅负责创建、验证并返回连接;
- main() 中获取 db 后立即使用 defer db.Close(),保证其生命周期覆盖全部数据库操作;
- Create() 返回 (db *sql.DB, error),增强错误处理健壮性;
- 补充了连接池配置与 Stmt/Rows 的显式清理(最佳实践);
- 使用 Query() 而非 Exec() 查询计数结果(Exec() 适用于 INSERT/UPDATE/DELETE,不返回行)。
⚠️ 注意事项:
- sql.DB 是并发安全的连接池抽象,本身不是单个连接,无需也不应频繁 Open/Close;
- defer db.Close() 应始终置于长期持有连接的最外层作用域;
- 生产环境建议添加 sslmode=disable 或配置 TLS(根据 PostgreSQL 服务要求);
- 永远检查 db.Ping() 结果,避免静默连接失败。
遵循以上模式,即可稳定复用数据库连接,彻底规避 "sql: database is closed" 错误。


















