
本文详解 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)这样既保持了全局共享,又实现了职责分离与错误集中处理。










