正确建立 mysql 连接需用 sql.open 或 gorm.open,显式导入驱动、严格配置 dsn(含 parsetime=true 和 loc=local),调用 db.ping() 验证,并通过 setmaxidleconns 等设置连接池;gin 不管理数据库,事务须手动控制且避免混用 db 实例。

用 sql.Open 建立连接,不是 gorm.Open
Gin 本身不处理数据库连接,它只是 HTTP 框架。真正负责连接 MySQL 的是 Go 标准库的 database/sql 包,或第三方 ORM(如 GORM)。如果你直接用原生 SQL,必须调用 sql.Open,传入驱动名(如 "mysql")和 DSN 字符串。常见错误是误以为 Gin 提供了 gin.DB() 这类方法,其实没有。
DSN 格式要严格: username:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local。漏掉 parseTime=True 会导致 time.Time 字段扫描失败;loc=Local 能避免时区错乱(尤其在 Docker 容器里默认 UTC)。
- MySQL 驱动必须显式导入:
_ "github.com/go-sql-driver/mysql" -
sql.Open不会立即建连,只做配置校验;真正验证要用db.Ping() - 别把
db实例放在 handler 里重复创建——它本就是并发安全、带连接池的全局对象
用 gorm.Open 时注意驱动注册和初始化选项
如果选 GORM(更常见),得先确保 MySQL 驱动已注册,再调用 gorm.Open。GORM v2 不再自动注册驱动,所以仍需 _ "github.com/go-sql-driver/mysql"。DSN 写法和原生一致,但 GORM 初始化额外需要 &gorm.Config{}。
容易被忽略的是连接池控制:GORM 默认复用 *sql.DB,但你得手动设置最大空闲/打开连接数,否则高并发下可能耗尽 MySQL 连接。比如:
sqlDB, err := db.DB()
if err != nil {
panic(err)
}
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
-
SetConnMaxLifetime必须设,MySQL 默认 wait_timeout=28800 秒(8 小时),但连接池可能复用超时连接,导致"invalid connection" - GORM 日志默认关闭,调试时加
logger.Default.LogMode(logger.Info)查 SQL - 别在
gin.Context里存*gorm.DB实例——用中间件注入到c.Set("db", db)更清晰
在 Gin 中安全传递数据库实例
最稳妥的方式是把 *gorm.DB 或 *sql.DB 作为依赖注入到路由处理器中,而不是全局变量或 context 传递。例如定义 handler 结构体:
type UserHandler struct {
DB *gorm.DB
}
func (h *UserHandler) List(c *gin.Context) {
var users []User
h.DB.Find(&users)
c.JSON(200, users)
}
这样单元测试可 mock DB,也避免并发读写冲突。若坚持用全局变量,至少确保初始化发生在 main() 开头、gin.Default() 之前,并加 sync.Once 防重入。
- 不要在每个请求里调用
sql.Open—— 连接池就废了,且易触发"dial tcp: lookup xxx: no such host" - 环境变量读取 DSN 时,记得
os.Getenv("DB_DSN")判空,空值直接 panic,别静默 fallback - Docker 部署时,MySQL 地址别写
127.0.0.1,要用服务名(如mysql:3306)
事务处理必须显式控制,Gin 不自动管理
Gin 对事务完全无感知。开启事务得靠 db.Begin(),后续所有操作必须用返回的 *gorm.DB 实例(含事务上下文),不能混用原始 db。常见坑是:在事务内调用了另一个函数,但该函数内部又用了全局 db 实例,导致部分语句没进事务。
正确做法是把事务 DB 实例作为参数透传,或用 gin.Context 存储(需配合中间件):
func TransactionMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tx := db.Begin()
c.Set("tx", tx)
defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
}
}()
c.Next()
if c.IsAborted() {
tx.Rollback()
return
}
tx.Commit()
}
}
- 事务内禁止调用
db.Save(),必须用c.MustGet("tx").(*gorm.DB).Save() - 嵌套事务不被 MySQL 原生支持,GORM 的
Session(&session)模式只是模拟,慎用 - 长事务会锁表,接口超时前务必保证
Commit()或Rollback()执行完毕











