gorm软删除需同时满足结构体定义*time.time类型的deletedat字段并加gorm:"index"标签、初始化时启用softdelete插件;查询自动过滤软删记录,仅unscoped()可绕过;unscoped().delete()为物理删除,不可逆。

软删除必须开启 GORM 的 gorm.DeletedAt 字段支持
默认情况下,GORM 不会把 DeletedAt 当作软删除字段——哪怕你加了这个字段,Delete() 仍会执行物理删除。关键在结构体标签和全局配置。
必须同时满足两个条件:
- 结构体中定义
DeletedAt字段,类型为*time.Time或time.Time,并加上gorm:"index"(否则查询时可能漏掉软删数据) - 初始化 DB 时启用软删除:用
GORM的SetupJoinTable不起作用,得靠gorm.Config{NowFunc: ...}或更直接的——确保没禁用SoftDelete插件(v2 默认已启用,但若手动关过就得重开)
常见错误现象:Delete() 后查不到记录,日志里却是 DELETE FROM xxx ——说明软删除根本没生效,大概率是字段没加 gorm 标签,或用了 Unscoped().Delete() 却误以为是默认行为。
查询时自动过滤已软删记录,但 Unscoped() 是唯一绕过方式
GORM v2 默认所有 Find、First、Where 等方法都会自动加 WHERE deleted_at IS NULL 条件。这不是魔法,是底层 SQL 构建时硬编码的逻辑。
所以别指望靠 Where("deleted_at IS NULL") 来“显式”过滤——它会被合并进已有条件,反而可能出错;也不要用 Where("1=1") 试图清空条件,没用。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 要查包括已软删的全部数据,必须显式调用
Unscoped(),且它得放在链式调用最前面:db.Unscoped().Where(...).Find(&users) -
Unscoped()仅对当前语句生效,不会污染后续操作 - 注意:关联查询(如
Preload)默认也受软删除影响,如果主表没软删但关联表有,且你又没Unscoped()关联表,就可能 preload 不到数据
DeletedAt 字段值为 nil 表示未删除,非 nil 才算软删
这是 GORM 软删除的判定依据,不是靠布尔字段,也不是靠时间大小比较。所以别手动生成 DeletedAt: time.Now() 去“模拟删除”——该用 Delete() 就用,它会自动赋值。
- 手动赋值
DeletedAt(比如user.DeletedAt = &now)再Save(),效果等同于Delete(),但绕过了 GORM 的软删除钩子(如BeforeDelete) - 想恢复软删记录?只需把
DeletedAt设为nil再Save(),不要用UpdateColumn(),否则可能跳过钩子或校验 - 字段类型用
*time.Time更安全:避免零值时间(0001-01-01)被误判为已删除
性能影响很小,但要注意:带 DeletedAt 的表,所有 SELECT 都多一个 WHERE 条件;如果高频查“已删除”数据,记得给 deleted_at 加数据库索引(gorm:"index" 会帮你建,但得确认建成功)。
慎用 Unscoped().Delete():它真删,且不可逆
这是最容易踩的坑——名字里带 Unscoped,动作却是物理删除。很多人以为只是“不走软删逻辑”,结果执行完发现记录彻底没了。
-
db.Unscoped().Delete(&user)→ 发出DELETE FROM users WHERE id = ?,跳过所有软删机制 - 想批量物理删除?可以,但务必确认业务允许,且最好加额外条件(如
Where("deleted_at )避免误删 - 没有“软删回滚”机制。GORM 不存删除前快照,
Unscoped().Delete()后只能靠数据库备份恢复
真正需要物理清理的场景极少,多数时候应该用定时任务定期 Unscoped().Where(...).Delete(&Model{}) 清理 N 个月前的软删数据,而不是在业务逻辑里混用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










