sql.open返回连接池句柄而非单次连接,不校验数据库可达性;db.ping()才真正触发网络握手和权限验证,必须显式调用以避免后续查询时错误难定位。

sql.Open 返回的是连接池句柄,不是单次连接 —— 它本身不校验数据库是否可达,DB.Ping() 才是真正触发网络握手和权限验证的一步。跳过 Ping() 容易在后续查询时才暴露连接失败,且错误堆栈更难定位。
用 sql.Open 初始化 MySQL 连接池
必须显式指定驱动名 "mysql",且 DSN 格式要严格匹配:user:password@tcp(host:port)/dbname?params。常见疏漏点:
-
parseTime=true必须加,否则time.Time字段会扫描失败(报sql: Scan error on column index 0) -
loc=Local或loc=Asia/Shanghai要明确,否则时间字段可能变成 UTC 或零值 - 密码含特殊字符(如
@、/)必须用url.PathEscape编码,否则 DSN 解析出错 - 数据库名不能留空,哪怕只想连上不选库 ——
sql.Open不支持无库连接
SetMaxIdleConns 和 SetMaxOpenConns 怎么设
这两个值不是越大越好。Iris 应用通常并发请求集中在 HTTP handler 内,而每个 handler 里 db.Query 或 db.Exec 都会从池中取连接。若设太高:
-
SetMaxOpenConns(100)但 MySQL 服务端max_connections只有 50 → 大量请求卡在等待连接,表现为超时或ERROR 1040 (HY000): Too many connections -
SetMaxIdleConns(50)但实际 QPS 很低 → 空闲连接长期占着 TCP 连接不释放,MySQL 端积累大量Sleep状态线程 - 推荐起始值:
SetMaxIdleConns(5)、SetMaxOpenConns(20),上线后根据SHOW PROCESSLIST和应用日志里的连接等待时间再调
为什么 DB.Ping() 要放在 iris.Application.Register 之前
Iris 的 app.Run() 是阻塞调用,一旦启动就接管了主线程。如果把数据库连接逻辑写在某个 handler 里(比如 app.Get("/api/user", handler)),第一次请求才会初始化 DB,此前所有依赖 DB 的初始化(如预热缓存、加载配置表)都会失败。
- 正确做法:在
main()函数顶部完成sql.Open+DB.Ping()+ 连接池调优,确认无误后再传给 Iris 实例 - 错误写法示例:
var db *sql.DB; app.Get("/ping", func(c iris.Context) { if db == nil { db = sql.Open(...) } })—— 并发请求可能同时触发多次sql.Open,导致连接池混乱 - 注意:
db.Close()不应在main()结束时立即调,因为 Iris 启动后仍需 DB;应监听 OS 信号,在进程退出前关闭
真正容易被忽略的是连接池的生命周期管理:Iris 没有内置 DB 关闭钩子,defer db.Close() 放在 main() 开头会立刻执行,必须用 os.Interrupt 信号捕获来兜底。否则进程重启时连接没释放,MySQL 端残留连接会越积越多。











