GORM字段映射需用gorm:"column:xxx"显式指定列名,否则查询为空;查不到记录时error为gorm.ErrRecordNotFound而非nil;Update默认跳过零值,批量更新推荐Select().Updates();事务中必须全程传递*gorm.DB参数。

定义模型时字段名必须匹配数据库列名,否则查询为空
GORM 默认按结构体字段名映射数据库列(snake_case),但如果你的表字段是 user_name,而结构体写成 UserName string,GORM 不会自动转成下划线命名——它会查 user_name 列,但生成的 SQL 里用的是 user_name 还是 username,取决于你有没有显式声明标签。
正确做法是统一用 gorm 标签指定列名:
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"column:name"`
Email string `gorm:"column:email"`
Age int `gorm:"column:age"`
}
常见错误现象:调用 db.First(&u) 返回 nil 但 u 字段全零值,实际数据存在;本质是字段没映射上,GORM 把空值塞进了结构体。
注意:gorm.Model(&User{}) 不会读取结构体标签,只用于指定表名,真正映射靠 db.First() 等操作时的反射解析。
立即学习“go语言免费学习笔记(深入)”;
创建记录前必须检查 ErrRecordNotFound,而不是直接判 nil
GORM 的 First、Take、Last 等方法查不到记录时,返回的 error 是 gorm.ErrRecordNotFound,不是 nil。很多人写成 if err != nil { ... } 就 panic,其实只是“没找到”,不该当异常处理。
典型误用:
var u User
err := db.First(&u, 999).Error
if err != nil { // ❌ 这里 err 是 ErrRecordNotFound,不是连接失败
panic(err) // 错误地中断流程
}
正确写法:
- 用
errors.Is(err, gorm.ErrRecordNotFound)判断是否未找到 - 或直接检查
result.RowsAffected == 0(result := db.First(...)) - 插入前用
db.Where("email = ?", email).First(&u).Error查重,再根据 error 类型决定创建还是返回已存在
Update 方法默认只更新非零值,批量更新需用 map 或 select
db.Save(&u) 和 db.Update("name", "new") 行为不同:Save 会把结构体所有字段(除主键和 gorm:"-:create")全量更新,而 Update 只改指定字段——但有个陷阱:如果字段类型是 int 且值为 0,GORM 默认跳过更新(认为是零值)。
例如:
u.Age = 0
db.Model(&u).Update("age", u.Age) // ✅ 显式指定字段,会更新为 0
db.Model(&u).Updates(u) // ❌ age 被跳过,因为 0 是 int 零值
db.Model(&u).Select("age").Updates(u) // ✅ 强制更新 age 字段
db.Model(&u).Updates(map[string]interface{}{"age": 0}) // ✅ map 不受零值过滤影响
性能影响:用 map 更新会丢失 GORM 的字段校验(比如 gorm:"not null" 不生效),生产环境建议优先用 Select() + 结构体。
事务中嵌套调用要传 *gorm.DB,不能复用全局 db 实例
全局 db *gorm.DB 是无状态的,但事务对象是带上下文的状态实例。如果你在事务函数里直接用全局 db.Create(),它不会跑在事务里,而是走独立连接,导致部分成功、无法回滚。
正确模式是:
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&user).Error; err != nil {
return err
}
if err := tx.Create(&profile).Error; err != nil {
return err
}
return nil
})
容易踩的坑:
- 把
tx当作普通db传给其他函数,但对方内部又用了全局db—— 必须确保整条调用链都接收*gorm.DB参数并透传 - 事务内调用第三方库或 HTTP 请求失败后,忘记 return error,导致事务自动提交
- MySQL 默认隔离级别是 REPEATABLE READ,长事务可能引发锁等待,
tx.Exec("SELECT ... FOR UPDATE")要谨慎
事务不是魔法盒,GORM 不会自动捕获 panic 并回滚,必须显式 return error 才触发 rollback。


















