sql.open 仅初始化驱动和连接池配置,真正连接在首次 query/exec 时建立;应在 main/init 中全局创建 *sql.db 并设合理池参数,避免 handler 中反复调用。

sql.Open 不是连接数据库,别在 handler 里反复调用
很多人看到 sql.Open 就以为“连上了”,结果在 HTTP handler 里每次请求都调一次,还紧跟 db.Close() —— 这等于彻底废掉连接池。真实情况是:sql.Open 只做两件事:加载驱动、初始化连接池配置;真正拨号发生在第一次 Query 或 Exec 时。
后果很直接:每请求新建连接 → 端口耗尽、TLS 握手堆积、goroutine 泄漏、MySQL 报 Too many connections。
- 全局声明一个
*sql.DB变量,在main或 init 函数中调用一次sql.Open -
db.Close()只应在服务退出前调用一次,不是每次查询后 - 用
db.Stats()观察OpenConnections和WaitCount,若突增说明连接池被绕过了
必须显式设置 SetMaxOpenConns、SetMaxIdleConns 和 SetConnMaxLifetime
默认值极具欺骗性:SetMaxOpenConns=0(无上限)、SetMaxIdleConns=2、SetConnMaxLifetime=0(永不过期)。高并发下极易打满 DB 连接数,或因 MySQL 主动断连(wait_timeout=8h)导致后续请求报 i/o timeout 或 connection refused。
-
SetMaxOpenConns按「峰值 QPS × 平均查询耗时(秒)」估算,例如 100 QPS × 0.2s = 20 → 设为 20 -
SetMaxIdleConns设为SetMaxOpenConns的 1/2~2/3,如 10,避免空闲连接长期占用 DB 资源 -
SetConnMaxLifetime(30 * time.Minute)必须加,防止 stale connection -
SetConnMaxIdleTime(30 * time.Second)建议加上,及时回收冷连接
查单行优先用 QueryRowContext,别用 Query + Scan 手动管理
QueryRowContext 和 Query 底层共享同一套逻辑,性能差异可忽略;但误用带来的副作用极严重:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 用
Query取单行却忘了rows.Close()→ 连接卡在池中无法复用 - 用
QueryRow查可能为空的数据却不检查err→Scan返回零值,sql.ErrNoRows被静默吞掉 - HTTP handler 中传
r.Context(),别用context.Background(),否则客户端取消后查询还在跑
正确姿势:err := db.QueryRowContext(r.Context(), "SELECT ...", args...).Scan(&v1, &v2),它自动处理资源释放,语义清晰。
批量操作别循环 QueryRowContext 或 Exec,改用 IN、VALUES 或 Prepare
逐行 db.QueryRowContext 查 N 个 ID,或循环 db.Exec("INSERT ...", x),是最常见的性能杀手。N+1 查询和 N 次网络往返会把延迟和连接压力拉满。
- 查多 ID:用
IN或JOIN,提前拼好参数占位符(如WHERE id IN (?, ?, ?)),再用db.Query - 批量插入:拼
VALUES多组,如INSERT INTO t(x) VALUES (?), (?), (?),一次Exec完成 - 高频重复 SQL:用
db.Prepare创建一次*sql.Stmt,复用执行;注意stmt.Close()应在服务退出前,而非每次调用后
这些点看似琐碎,但线上服务的连接泄漏、超时抖动、CPU 毛刺,十有八九就卡在其中一两个没设对的池参数,或某处多写了半行 sql.Open。细节不落地,优化就是纸面功夫。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










