推荐方式是database/sql配合github.com/go-sql-driver/mysql;sql.open不报错因仅初始化连接池,真实连接在db.ping()或首次query时建立;dsn须含tcp()、parsetime=true、loc=local,密码特殊字符需url.queryescape;连接池需设maxopenconns、maxidleconns、connmaxlifetime;query多行须调用rows.next()、rows.close(),null字段用sql.nullstring等处理。

database/sql 配合 github.com/go-sql-driver/mysql 是唯一推荐的连接方式,其他路径(如手写 TCP、C 绑定、自研协议)在稳定性、安全性和维护性上都不可行。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
为什么 sql.Open 不报错但查不出数据?
sql.Open 只解析 DSN、注册驱动、配置连接池,并不建立真实连接。哪怕 MySQL 进程没启动、密码错、端口填成 3307,它也返回 *sql.DB 且 err == nil。
真正建连发生在第一次调用 db.Ping()、db.Query() 或 db.Exec() 时。
常见现象:
- 本地跑通,部署后 dial tcp: i/o timeout(DNS 解析失败或容器网络不通)
- 日志里没报错,但业务查询始终返回空或 panic:sql: no rows in result set
必须在 sql.Open() 后立即加:
if err := db.Ping(); err != nil {
log.Fatal("failed to connect to MySQL:", err)
}
DSN 字符串怎么写才不踩坑?
格式固定为:user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true&loc=Local
- 必须带 tcp(),不能写成 mysql:// 或漏掉括号
- & 是 URL 中的 & 符号,不是 HTML 实体 &;若在 XML/HTML 环境中拼接,需提前转义
- parseTime=true 是刚需:没有它,MySQL 的 DATETIME 字段会以 []byte 形式返回,Scan 到 time.Time 直接 panic
- loc=Local 适合开发;生产建议用 loc=UTC,由应用层统一做时区转换,避免服务器时区不一致导致时间偏移
- 密码含特殊字符(如 @、/)时,必须用 url.QueryEscape 处理,否则 DSN 解析失败
连接池参数不设等于埋雷
默认值极危险: -MaxOpenConns = 0(无限制)→ 容易打爆 MySQL 的 max_connections
- MaxIdleConns = 2 → 空闲连接太少,高并发下频繁新建连接
- ConnMaxLifetime = 0(永不过期)→ 云数据库(如阿里云 RDS)常设 wait_timeout=300s,复用过期连接会报 invalid connection
正确做法(上线前必配):
- db.SetMaxOpenConns(25)
- db.SetMaxIdleConns(25)(与 Open 值一致,避免空闲连接被过早回收)
- db.SetConnMaxLifetime(4 * time.Minute)(必须小于 MySQL 的 wait_timeout)
Query 多行时最常漏的三件事
- 忘记调用rows.Next():直接 rows.Scan() 会 panic,错误信息可能是 sql: Scan called without calling Next
- 忘记 rows.Close():即使循环读完所有行,也必须显式关闭,否则连接不归还池,很快触发 too many open files 或卡死
- 用原生类型接 NULL 字段:MySQL 允许 NULL,但 string、int 无法表达“空”语义;必须用 sql.NullString、sql.NullInt64 等,并检查 .Valid 字段
示例:
rows, err := db.Query("SELECT name, age FROM users")
if err != nil {
return err
}
defer rows.Close() // 关键
<p>for rows.Next() {
var name sql.NullString
var age sql.NullInt64
if err := rows.Scan(&name, &age); err != nil {
return err
}
if name.Valid {
fmt.Println(name.String)
}
}</p>
复杂点在于:连接池参数、DSN 时区、NULL 处理这三处,任何一个没对齐,都会在压测或上线后突然暴露,而且错误现象分散(超时、空值、连接耗尽),很难一次性定位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










