恢复软删除记录必须用unscoped().update("deleted_at", nil),因save()和普通update()不更新受控的deletedat字段;且该字段必须为*time.time类型,否则零值会被误判为已删除。

恢复前必须确认 DeletedAt 是 *time.Time 类型
很多恢复失败的根源是字段类型不对。GORM 只认 DeletedAt gorm.DeletedAt 或 *time.Time,如果定义成 time.Time(值类型),新插入记录的 DeletedAt 默认是零值 0001-01-01 00:00:00 +0000 UTC,GORM 会把它当“已删除”,导致刚建的记录就查不到,更别提恢复了。
检查结构体是否符合要求:
- ✅ 正确:
DeletedAt *time.Time `gorm:"index"`或嵌入gorm.Model - ❌ 错误:
DeletedAt time.Time、IsDeleted bool、deleted_at sql.NullTime - ⚠️ 注意:
gorm:"index"建议加上,否则WHERE deleted_at IS NOT NULL查询可能变慢
恢复操作必须用 Unscoped().Update("deleted_at", nil)
Save() 和普通 Update() 都不会更新 DeletedAt 字段——GORM 把它当作受控字段,直接赋值 user.DeletedAt = nil 再调用 Save() 是无效的。
正确写法只有这一种模式:
db.Unscoped().Model(&user).Where("id = ?", 123).Update("deleted_at", nil)
-
Unscoped()必须放在最前面,否则 WHERE 条件可能匹配不到已软删记录(因为默认查询会加deleted_at IS NULL) -
Update("deleted_at", nil)的第二个参数必须是nil,不是time.Time{},否则写入零时间仍被判定为已删除 - 批量恢复也一样:
db.Unscoped().Model(&User{}).Where("deleted_at IS NOT NULL AND created_at
查不到要恢复的记录?先用 Unscoped() 把它捞出来
你不能对一个查不到的记录执行恢复。常规 First()、Find() 都会自动跳过软删除数据,所以得先绕过过滤:
var user User
err := db.Unscoped().Where("id = ?", 123).First(&user).Error
if err != nil {
// 处理记录不存在或已物理删除
}
// 确认 user.DeletedAt != nil 后再恢复
- 别省略这一步:避免对本就未删除或已物理删除的记录重复执行
Update("deleted_at", nil) -
Unscoped()是链式透传的,如果用了Preload(),关联表也会跳过软删除过滤,这点容易引发意外数据暴露 - 后台管理或回收站场景才适合用
Unscoped();普通用户接口不该暴露已删数据
恢复后 Count 不一致?注意默认行为和业务语义
恢复操作本身不难,但容易忽略后续影响。比如业务需要统计“总注册用户数”,而 Count() 默认不包含软删除记录——恢复后如果不重新计数,线上数据可能对不上。
- 查总数(含已删):必须显式加
Unscoped().Count() - 查有效数(不含已删):用默认
Count()即可 - 归档场景下,恢复只是临时操作;长期保留大量
DeletedAt != nil记录会导致主表膨胀、索引变慢、备份体积增大
真正麻烦的不是怎么恢复,而是恢复之后要不要同步清理历史数据、要不要触发关联状态重算、以及有没有审计日志记录这次操作——这些不在 GORM 自动机制里,得靠业务层兜底。











