GORM v2 上手之初,最常把人卡住的,无非就三件事:连不上、查不到、建不成表。只要把初始化和模型定义那几条硬规矩摸清楚,后面的增删改查基本就是顺水推舟的事。

gorm.Open 为什么一运行就 panic
不是DSN写错了,而是用了GORM v1的写法却装了v2——gorm.Open("mysql", dsn) 在v2中已彻底移除,直接报 unsupported driver。那正确的做法是什么?
- 必须先用
sql.Open拿到*sql.DB,再传给gorm.Open - 驱动要包一层:
mysql.New(mysql.Config{Conn: sqlDB}),不能直接传mysql.Open(dsn) sqlDB.SetMaxOpenConns()和SetMaxIdleConns()必须在gorm.Open前调,否则无效- DSN 中的
&是普通字符(Go 字符串里写&即可),不是 HTML 实体&
AutoMigrate 为什么没建表 or 字段全是 id/created_at
结构体字段根本没被 GORM 识别出来,它退回到了默认字段集。主键和字段命名是硬门槛,绕不过去。
- 主键字段名必须是
ID(首字母大写),或显式加标签:ID uint `gorm:"primaryKey"`;id、Id、UserId都不认 - 非主键字段默认转蛇形小写:
UserName→user_name;想保留原名得加gorm:"column:UserName" - 如果不想自动复数化表名(
users→user),初始化时加配置:&gorm.Config{NamingStrategy: schema.NamingStrategy{SingularTable: true}} AutoMigrate不删字段、不改类型,只新增字段和索引;想重试请先db.Migrator().DropTable(&User{})
Where 查询传 struct 为什么查不到数据
Where(&User{Name: "Alice", Age: 0}) 这种写法会把所有字段(包括零值)塞进 WHERE 条件,最终生成 WHERE name = 'Alice' AND age = 0,而你本意可能是“只按非空字段过滤”。那怎么避免?
- GORM 对 struct 的零值字段(
0、""、false、time.Time{})**不忽略**,而是照常参与条件拼接 - 安全做法是链式调用:
Where("name = ?", name).Where("age > ?", 0) - 动态条件建议手动构建
map[string]interface{},只放非零/非空的 key-value - 字段名大小写敏感:数据库列是
user_name,struct tag 就得写gorm:"column:user_name"
CreateInBatches 批量插入要注意什么
这是 GORM v2 官方推荐的批量插入方式,但默认行为容易引发数据不一致。需要留心几个点:
- 别用
db.Create(&users)传切片——v2 默认仍是一条一条插,性能差且易耗尽连接池 CreateInBatches默认不开启事务,某一批失败,前面批次已提交;如需强一致性,必须显式包裹db.Transaction()- MySQL 不支持
RETURNING,所以即使加了Clauses(clause.Returning{}),ID 也不会回填到 struct 切片中 - 批次大小建议设为 100–500;太小失去批量意义,太大可能触发 MySQL
max_allowed_packet限制
还有一个容易被忽略的细节:GORM v2 的钩子(如 BeforeCreate)默认不被 Sa ve() 触发,必须用 Create() 或 Update();还有读写分离场景下,事务内所有操作强制走主库——哪怕你只写了 Find(),也得注意时序一致性。