gin 不处理数据库连接,需用 database/sql 配 mysql 驱动;sql.open 仅校验 dsn,须 db.ping() 测试连通;*sql.db 应挂载到 gin 上供 handler 使用;query 返回的 rows 必须显式 close() 防泄漏;queryrow 自动关闭;scan 须严格匹配字段顺序、类型及 null 处理;事务中必须统一使用 tx 对象,避免混用 db 和 tx。

Go Gin 里用 database/sql 连 MySQL,不是用 Gin 自己连
Gin 本身不处理数据库连接,它只管 HTTP 路由和响应。真正连 MySQL 的是 Go 标准库 database/sql,配合 MySQL 驱动(比如 github.com/go-sql-driver/mysql)。很多人卡在第一步:以为 Gin 提供了 gin.DB() 这种方法,其实没有。
实操建议:
- 先
go get github.com/go-sql-driver/mysql安装驱动 - 用
sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/dbname?parseTime=true&loc=Local")初始化连接池,注意参数里parseTime=true和loc=Local要加,否则time.Time字段可能解析失败或时区错乱 - 别忘了调
db.Ping()检查连通性,sql.Open只校验 DSN 格式,不真正建连 - 把
*sql.DB实例挂到 Gin 的gin.Engine上,常用方式是engine.Use(func(c *gin.Context) { c.Set("db", db) }),后续 handler 里用c.MustGet("db").(*sql.DB)取
Query 和 QueryRow 返回的 sql.Rows 必须显式 Close()
这是最常被忽略的资源泄漏点。Gin handler 里写 rows, err := db.Query("SELECT ...") 后,如果没调 rows.Close(),连接会一直占着,很快耗尽连接池(尤其高并发时)。
常见错误现象:
- 请求变慢、超时,日志里出现
dial tcp 127.0.0.1:3306: connect: cannot assign requested address - MySQL 侧看到大量
Sleep状态连接
正确做法:
- 用
defer rows.Close()—— 但注意:必须在for rows.Next()循环结束后再 defer,否则循环中途就关了 - 更稳妥的是把
rows.Close()放在 for 循环之后、函数 return 前,或者用if rows != nil { defer rows.Close() }包一层 -
QueryRow不需要手动 Close,它内部自动处理
用 Scan 读数据时,变量顺序、类型、数量必须和 SELECT 字段严格一致
MySQL 查询字段顺序变了、加了新字段、用了别名但没对应上变量,Scan() 就会直接 panic 或静默失败(比如把 int 扫成 string),而且错误信息往往不明确。
使用场景举例:你写 SELECT id, name, created_at FROM users,那 Scan(&id, &name, &createdAt) 三个变量顺序不能错,且 createdAt 类型得是 time.Time(前提是 DSN 里开了 parseTime=true)。
容易踩的坑:
- SELECT 里用了函数或表达式(如
UNIX_TIMESTAMP(created_at)),结果类型是int64,但你还用time.Time变量去 Scan → panic - 字段有 NULL,但用非指针类型接收(如
string而非*string)→sql: Scan error on column index 1 - 用
SELECT *,表结构一变,代码立刻崩,别这么干
事务里别混用 db.Query 和 tx.Query
一旦开始事务(tx, _ := db.Begin()),所有操作必须走 tx 对象,比如 tx.Query、tx.Exec。如果在事务中误用了 db.Query,那这条语句根本不在事务里,也不会受 tx.Commit() 或 tx.Rollback() 控制。
性能与一致性影响:
- 看似成功提交,其实部分数据已落库(
db.Query那条),事务原子性彻底失效 - 调试时很难发现,因为没报错,只是逻辑错乱
实操建议:
- 事务内统一用
tx;handler 函数开头就判断是否在事务上下文里(比如通过c.Get("tx")),避免混用 - 事务结束前,记得
tx.Commit()或tx.Rollback(),后者尤其重要——panic 或 error 时没 rollback,连接就卡死在事务中 - 别依赖 defer 做 rollback:
defer tx.Rollback()在 Commit 成功后也会执行,导致二次 rollback 报错
真正的难点不在语法,而在连接生命周期管理、资源释放时机、事务边界控制这些“看不见”的地方。写完一行 db.Query,得立刻想清楚:它什么时候关?在哪关?会不会和其他操作串了?
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











