sql.Open仅初始化连接池而不建立真实连接,需显式调用db.Ping()(推荐PingContext)验证连通性;import _ "github.com/go-sql-driver/mysql"用于注册驱动名“mysql”;DSN中用户名密码须URL编码;sql.DB是连接池而非单连接,应全局复用并合理设置池参数。

sql.Open 之后不等于数据库已连上
很多人以为 sql.Open 会立即建立真实连接,结果在后续 Query 或 Exec 时才爆出 dial tcp: i/o timeout 或 connection refused。这是因为 sql.Open 只是初始化连接池配置,真正校验连接要靠 db.Ping()。
- 必须显式调用
db.Ping()做启动时连通性检查,否则故障会延迟暴露 -
db.Ping()默认有 30 秒超时(受底层驱动和网络影响),生产环境建议加 context 控制:db.PingContext(ctx, time.Second * 5) - 如果用的是
github.com/lib/pq(PostgreSQL)或github.com/go-sql-driver/mysql(MySQL),驱动本身不自动重试,Ping失败就是失败,得自己处理重试逻辑或 fallback
import _ "github.com/go-sql-driver/mysql" 是干啥的
这行看似没用的 import 实际上是在注册驱动,让 database/sql 知道 “mysql” 这个驱动名对应哪个实现。不加这句,sql.Open("mysql", dsn) 会直接 panic:sql: unknown driver "mysql"。
- 下划线导入(
import _ "xxx")只执行包的init()函数,不引入任何变量或类型 - 不同驱动注册名不同:MySQL 是
"mysql",PostgreSQL 是"postgres",SQLite 是"sqlite3",拼错一个字符就报未知驱动 - Go 1.21+ 支持原生 SQLite 驱动
database/sql/sqlite3,但多数项目仍用github.com/mattn/go-sqlite3,它注册的仍是"sqlite3"
DSN 字符串里 password 含特殊字符会炸
MySQL 和 PostgreSQL 的 DSN 都是 URL 形式,@、/、:、? 这些符号如果不编码,会导致解析错乱。比如密码是 pa@ss/w?rd,直接拼进 DSN:user:pa@ss/w?rd@tcp(127.0.0.1:3306)/db,会被当成多个字段切分。
- 必须对 username 和 password 单独做 URL 编码,用
url.QueryEscape,不是url.PathEscape - 推荐封装一个函数生成 DSN,避免手拼:
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true", url.QueryEscape(user), url.QueryEscape(pass), addr, dbname) - PostgreSQL 的 DSN 支持更灵活的格式(如
postgresql://user:pass@host/db?sslmode=disable),同样要编码 password,且sslmode=disable在本地开发时经常漏加,导致连接 hang 住
sql.DB 不是单个连接,别把它当 connection 传参
sql.DB 是连接池抽象,不是“一个连接”的 wrapper。常见错误是把它塞进 struct 当字段,然后在方法里反复调用 db.QueryRow().Scan() 却不关 rows,或者误以为 db.Close() 是“断开当前连接”——其实它是关闭整个池,之后再用会 panic。
立即学习“go语言免费学习笔记(深入)”;
-
db.QueryRow()返回的*sql.Row不需要 Close;db.Query()返回的*sql.Rows必须调用rows.Close(),否则连接不会归还池中 - 连接池大小默认是 0(无上限),高并发下可能耗尽数据库连接,务必设置:
db.SetMaxOpenConns(20)、db.SetMaxIdleConns(10) - 不要把
sql.DB当成短生命周期对象传递,它本该是全局或长生命周期实例;也不要在 HTTP handler 里每次 new 一个sql.DB,那是资源泄漏的温床
连接池参数、DSN 编码、驱动注册这三个点,漏掉任何一个,程序都可能在上线后某天凌晨三点开始静默失败。











