sql.open仅校验dsn格式和驱动注册,不建立真实连接,真正建连延迟至db.ping()或首次query时;必须立即调用db.ping()验证连通性,否则错误延迟暴露。

Go 连 MySQL 不是配好驱动就能查出数据,最常卡住的地方是:sql.Open 不报错但后续查询 panic、time.Time 字段扫成 []byte、rows 忘关导致连接池耗尽。
sql.Open 为什么总成功,但 db.Query 却 panic?
因为 sql.Open 只校验 DSN 格式和驱动注册状态,不建 TCP 连接。哪怕 MySQL 进程没起、密码错、端口填成 3307,sql.Open 都返回非 nil 的 *sql.DB 且 err == nil。
真正首次建连发生在:db.Query、db.QueryRow、db.Exec 或显式调用 db.Ping() 时。错误会延迟抛出,可能出现在业务逻辑深处,难以定位。
- 必须在
sql.Open后立刻加db.Ping()做连通性验证 - 漏掉这步,上线后可能第一个请求就 crash,而不是初始化阶段暴露问题
- 云数据库(如阿里云 RDS、腾讯云 CDB)常设较短的
wait_timeout,更需靠db.Ping()提前发现连接失效
parseTime=true 和 loc=Local 到底怎么配?
MySQL 的 DATETIME 字段默认以字符串形式返回,不转 time.Time;而时区处理不当会导致时间偏移 8 小时(尤其用 Asia/Shanghai 时)。
DSN 中这两项必须成对出现:
-
parseTime=true:启用时间解析,否则Scan到*time.Time会报错:sql: Scan error on column index 0, name "created_at": unsupported Scan, storing driver.Value type []uint8 into type *time.Time -
loc=Local:用本机时区解析(适合开发),生产环境建议用loc=UTC,应用层统一做时区转换,避免多台服务器时区不一致 - 写错参数名(比如
parsetime=true少个=或大小写不对)会导致配置静默失效
完整 DSN 示例:user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Local
rows.Close() 忘了会怎样?
db.Query 返回的 *sql.Rows 是资源句柄,不是一次性数据容器。即使你 for rows.Next() 遍历完了所有行,底层 TCP 连接也不会自动归还给连接池。
- 不调
rows.Close()→ 连接一直被占用 → 达到SetMaxOpenConns上限后,后续请求阻塞或超时 - 典型错误写法:
rows, _ := db.Query(...); for rows.Next() { ... },缺defer rows.Close()或rows.Close() - 正确做法:在
Query后立即defer rows.Close(),或确保在所有分支路径都调用rows.Close()
注意:rows.Close() 可安全重复调用,但不能在 rows.Next() 循环中途提前 Close,否则剩余数据读不到。
事务里 db.Begin() 后,函数调用怎么不生效?
GORM 或原生 database/sql 的事务对象是独立实例,不是全局上下文。如果在事务中调用另一个函数,并传入原始 *sql.DB(而非 *sql.Tx),那个函数内部的 db.Query 就脱离了事务,走的是新连接。
- 原生事务:用
tx, err := db.Begin(),后续所有操作必须用tx.Query、tx.Exec,不能混用db - GORM 事务:用
tx := db.WithContext(ctx).Begin(),并在子函数中接收并使用该*gorm.DB实例,不能重新db.Where(...) - 常见坑:封装了
getUserByID(db, id)工具函数,但在事务里传了db而非tx,导致查询不在事务隔离内
事务没 Commit 或 Rollback,连接不会释放,也容易触发锁等待或死锁。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











