gorm软删除必须使用gorm.deletedat类型并加索引,嵌入gorm.model最稳妥;delete()依赖有效主键,否则可能误删全表;默认查询自动过滤软删数据,查软删记录需unscoped();恢复需设deletedat为nil;须建索引、手动处理级联、定期归档。

软删除不是加个字段就行,必须用 gorm.DeletedAt
GORM 的软删除不是“你写个 deleted_at 字段它就自动生效”,它只认 gorm.DeletedAt 类型。哪怕你定义成 *time.Time 或 time.Time,只要没用这个特定类型,GORM 就当普通字段处理,Delete() 仍会物理删除。
正确做法是直接嵌入 gorm.Model——它已预置 ID、CreatedAt、UpdatedAt 和带索引的 DeletedAt:
type User struct {
gorm.Model // ✅ 自动启用软删除
Name string
}
如果非要自定义结构体,必须严格匹配:
-
DeletedAt gorm.DeletedAt `gorm:"index"`——gorm:"index"不可省,否则非Unscoped()查询会漏掉该行 - 不能手动生成时间赋值给
DeletedAt,比如user.DeletedAt = time.Now(),这不算软删除,后续Find()还可能查到 - 别用
sql.NullTime或自定义时间包装类型,GORM 不识别
Delete() 默认软删,但主键为空会删全表
调用 Delete() 时,GORM 依赖主键定位记录。如果传入的 struct 实例 ID 是零值(比如 0 或空字符串),GORM 会误判为“无条件删除”,执行类似 DELETE FROM users WHERE id = 0 ——而多数数据库会把它当作无匹配行;但某些场景(如使用复合主键或未设主键)可能触发全表删除,非常危险。
- 务必确保被删对象的主键字段有有效值,推荐先
First()查出再删 - 批量删除用
Where().Delete(&User{})更安全,它不依赖实例主键,而是拼接 WHERE 条件 - 物理删除必须显式加
Unscoped(),否则即使主键有效也只会软删
查不到软删数据?不是 bug,是默认行为
这是 GORM 软删除机制的设计前提:所有常规查询(First()、Find()、Count() 等)自动追加 WHERE deleted_at IS NULL。所以你“查不到”,恰恰说明软删除生效了。
- 要查软删记录,必须用
Unscoped():db.Unscoped().First(&u, 123) - 恢复记录本质是把
DeletedAt设为nil:db.Unscoped().Model(&u).Update("deleted_at", nil) - 注意
Unscoped()是链式方法,它会影响之后所有操作,别漏掉点号或写错位置 - 事务中恢复+更新要一起做,否则中间状态可能被其他协程读到脏数据
索引、性能和级联,三个容易被跳过的现实问题
软删除让数据“隐形”,但没让它变轻。大量软删记录堆积,不加索引会导致查询越来越慢;外键关联没处理好,还会引发数据不一致。
-
DeletedAt字段必须建索引(gorm:"index"已帮你做了,但手动定义时别忘) - 级联软删除需手动实现:GORM 不自动对
has_many关系执行软删,得自己Where("user_id = ? AND deleted_at IS NULL").Delete(&Order{}) - 定期归档或物理清理软删数据是必要运维动作,别只靠
Unscoped()撑着
软删除看着简单,但字段类型、主键校验、查询边界、索引覆盖、关联一致性,五处都卡住就真删不干净了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











