gorm v2 中 gorm.open 不再接受驱动名和 dsn 字符串,必须先 sql.open 获取 *sql.db 再传入 gorm.open(mysql.new(...)),且连接池配置需在 gorm.open 前设置。

gorm.Open 为什么一运行就 panic?
绝大多数人卡在这一步,不是 DSN 写错了,而是用了 GORM v1 的写法却装了 v2 —— gorm.Open("mysql", dsn) 在 v2 中已彻底移除,直接 panic 报 unsupported driver。
正确做法是:先用 sql.Open 拿到 *sql.DB,再传给 gorm.Open:
import (
"database/sql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
<p>dsn := "user:pass@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
sqlDB, err := sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
db, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB}), &gorm.Config{})</p>
- 别漏掉
mysql.New(...)包裹,这是 v2 的驱动适配层 -
sqlDB.SetMaxOpenConns和SetMaxIdleConns要在gorm.Open前调,否则无效 - MySQL 8.0+ 若报外键错误,加
gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}
Create 和 Save 都能插数据,到底该用哪个?
Create 是明确的“插入新记录”,Save 是“保存当前结构体状态”——它会根据主键是否存在自动选择 INSERT 或 UPDATE。但这个“自动判断”在没主键或主键为零值时极不可靠。
常见错误现象:Save(&user) 插入后发现 ID 是 0、或重复插入同一条记录、或静默覆盖了不该动的字段。
- 新增场景一律用
Create:它不看主键,只管插入;失败时res.Error明确,res.RowsAffected可信 - 想指定字段插入?用
Select("name", "price").Create(&good) - 想忽略某些字段(比如不想写默认时间)?用
Omit("created_at", "updated_at").Create(&good) - 批量插入直接传切片:
db.Create(&goods),比循环快且事务安全
Where 条件里传 map 或 struct,为啥查不到数据?
GORM 对 Where(map[string]interface{}{"name": "", "age": 0}) 的处理是:把每个 key 当字段名、value 当参数值,**零值照常参与条件生成**,最终 SQL 变成 WHERE name = '' AND age = 0,而不是你想要的“忽略空值”。
更危险的是 struct:Where(&User{Name: "a"}) 会把所有字段(包括零值的 ID、Age)都塞进 WHERE,极易误过滤。
- 安全写法是链式调用:
Where("name != ?", "").Where("age > ?", 0) - 需要动态拼条件?手动构建
map[string]interface{},只放非零/非空的 key-value - 字段名大小写敏感:数据库列是
user_name,struct tag 是gorm:"column:user_name",但Where(&User{UserName: "x"})不会自动映射,仍按字段名UserName查 —— 所以别依赖 struct 自动转列名
Update 用 Save 还是 Updates?零值怎么保得住?
Save 是全量更新:它把整个 struct 所有字段(含零值)都写进 SQL;Updates 是部分更新:默认跳过零值字段(string 空、int 为 0、bool 为 false),但注意:int 类型的 0 就是合法业务值(比如库存为 0),这时 Updates 会把它当“未设置”跳过。
- 前端 PATCH 只传部分字段 → 用
db.Model(&user).Updates(updateMap),其中updateMap是显式构造的map[string]interface{} - 必须允许字段设为 0?把字段改成指针:
Stock *int,这样nil表示不更新,&zero表示设为 0 - 想原子更新(比如库存减 1)?用
db.Model(&product).Update("stock", gorm.Expr("stock - 1")),避免查-改-写竞态
最易被忽略的点:事务里混用全局 db 和事务 tx 实例。比如在 tx := db.Begin() 里调了 config.DB.Create,那这条记录根本不在事务中 —— 数据库层面已提交,回滚也救不回来。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











