主键未回填需检查结构体主键标签和指针传参:确保主键字段含gorm:"primaryKey"标签(如ID uint \gorm:"primaryKey"\`),且db.Create()必须传地址&user;传值或缺标签将导致user.ID`保持0。

db.Create() 插入数据时主键没回填?检查结构体字段标签和指针传参
常见现象是 user.ID 为 0,即使数据库已成功插入且自增 ID 已生成。根本原因通常是:结构体字段没加 gorm:"primaryKey" 标签(或没嵌入 gorm.Model),或调用时传了值而非地址。
实操建议:
- 确保主键字段有
gorm:"primaryKey",例如ID uint `gorm:"primaryKey"` -
db.Create()必须传结构体指针:db.Create(&user),传user值会静默失败且不回填 ID - 若用
gorm.Model,它已内置ID uint、CreatedAt等字段,可省去重复定义 - 批量插入
db.Create(&users)同样要求&users是切片指针,否则只插第一条
条件更新用 db.Where().Updates() 还是 Save()?看是否要跳过零值
Save() 会无条件更新所有字段(包括 0、""、false),而 Updates() 默认跳过零值字段——这是多数业务场景的真实需求,比如只改邮箱不碰密码和状态。
实操建议:
- 只想更新部分字段且忽略零值:用
db.Where("id = ?", id).Updates(&user) - 强制更新所有字段(含零值):用
db.Where("id = ?", id).Select("*").Updates(&user) - 避免误更新整行:永远不要对未初始化的 struct 调用
Save(),它可能把默认零值刷进数据库 - 注意
Updates(map[string]interface{})不触发钩子函数,Updates(&struct)会触发
软删除后 db.First() 查不到记录?默认作用域已过滤 DeletedAt
GORM V2 默认启用软删除,First()、Find() 等查询自动添加 WHERE deleted_at IS NULL 条件。这不是 bug,是设计行为。
实操建议:
- 查包含已软删除的记录:用
Unscoped(),例如db.Unscoped().Where("id = ?", id).First(&user) - 彻底物理删除:用
Unscoped().Delete(),例如db.Unscoped().Where("id = ?", id).Delete(&User{}) - 恢复软删除记录:更新
DeletedAt为 nil,db.Unscoped().Where("id = ?", id).Update("deleted_at", nil) - 别在模型里删掉
DeletedAt gorm.DeletedAt字段——否则软删除功能整体失效,且Unscoped()失去意义
db.CreateInBatches 插入万级数据仍慢?关键在批量大小和事务控制
直接传 10000 条进 Create() 可能 OOM 或超 MySQL 包大小限制;但设成每批 1,性能又退化成单条插入。中间值需实测,且默认不自动开事务。
实操建议:
- 推荐批次大小 100–500,例如
db.CreateInBatches(users, 200) - 手动包事务提升吞吐:
db.Transaction(func(tx *gorm.DB) error { return tx.CreateInBatches(users, 200).Error }) - 避免在循环里反复调用
CreateInBatches——每调用一次都是一次独立 SQL 请求 - 如果字段固定且无钩子需求,原生
Exec()拼批量 INSERT 更快,但失去 GORM 的类型安全和钩子能力
Statement 对象;一旦写错顺序(比如先 Where() 再 Model()),条件可能被丢弃,这类问题往往没有报错,只查不到数据。


















