如何在 Go 中正确共享数据库连接句柄(db)

3次阅读

如何在 Go 中正确共享数据库连接句柄(db)

本文详解 go 程序中因变量作用域误用导致全局 *sql.db 为 nil 的典型错误,并提供安全、可复用的数据库初始化与共享方案。

你在 go 中定义了全局变量 var db *sql.DB,本意是让所有函数(如 getNames)都能访问同一个数据库连接池。但问题出在 main 函数内这一行:

db, err := sql.Open("sqlite3", "./data.db")

这里使用了短变量声明 :=,它会创建一个新的局部变量 db,而非给全局变量赋值。因此,全局 db 仍为 nil,而 getNames() 调用 db.Query(…) 时触发空指针解引用 panic。

✅ 正确做法是:显式使用赋值操作符 = 并确保 err 已预先声明,从而真正初始化全局 db:

package main  import (     "database/sql"     "fmt"     "log"     _ "github.com/mattn/go-sqlite3" )  var db *sql.DB // 全局数据库句柄(连接池)  func main() {     fmt.Println("Starting test ...")      var err Error     db, err = sql.Open("sqlite3", "./data.db") // ✅ 使用 = 赋值,作用于全局 db     checkErr(err)      err = db.Ping() // 验证连接是否可用(非必须但强烈推荐)     checkErr(err)      fmt.Println(getNames()) }  func checkErr(err error) {     if err != nil {         log.Fatal(err)     } }  func getNames() []string {     query := `SELECT name FROM places`     rows, err := db.Query(query) // ✅ 此时 db 已初始化,非 nil     checkErr(err)     defer rows.Close()      var names []string     for rows.Next() {         var name string         if err := rows.Scan(&name); err != nil {             checkErr(err)         }         names = append(names, name)     }      // 检查 rows.Next() 循环后的潜在错误(如列类型不匹配)     if err := rows.Err(); err != nil {         checkErr(err)     }      return names }

? 关键注意事项:

  • sql.Open 不会立即建立连接,它只返回一个数据库连接池的句柄;务必调用 db.Ping() 进行首次健康检查。
  • defer rows.Close() 必须在 for rows.Next() 循环之后、函数返回前调用,否则可能提前关闭结果集。
  • rows.Scan() 后应检查其返回的 error(示例中已补充),且循环结束后需调用 rows.Err() 判断扫描过程是否有错。
  • 若项目结构变复杂(如含多个包或 http 服务),建议将 db 封装进配置结构体或使用依赖注入,避免过度依赖全局变量。

? 进阶建议(生产环境):
为提升可维护性与测试性,可将数据库初始化提取为独立函数,并支持自定义连接参数:

func initDB(dataSourceName string) (*sql.DB, error) {     db, err := sql.Open("sqlite3", dataSourceName)     if err != nil {         return nil, err     }     if err = db.Ping(); err != nil {         db.Close()         return nil, err     }     // 可选:设置连接池参数     db.SetMaxOpenConns(10)     db.SetMaxIdleConns(5)     return db, nil }

然后在 main 中调用:

var err error db, err = initDB("./data.db") checkErr(err)

这样既保持了全局共享,又实现了职责分离与错误集中处理。

text=ZqhQzanResources