软删除字段必须是gorm.deletedat类型;gorm仅识别名为deletedat且类型为*time.time的字段,自定义字段名将导致软删除失效,正确做法是嵌入该字段并建议加gorm:"index"索引。

软删除字段必须是 gorm.DeletedAt 类型
GORM 的软删除机制是“约定优于配置”,它只识别名为 DeletedAt 且类型为 gorm.DeletedAt(即 *time.Time)的字段。如果你自定义字段名(如 IsDeleted 或 DeletedTime),GORM 默认不会启用软删除逻辑,查询时仍会返回已“删除”的记录。
正确做法是直接嵌入 GORM 提供的字段:
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"` // 必须这样写
}
注意:gorm:"index" 不是必须,但强烈建议加——否则执行 Unscoped().Where("deleted_at IS NOT NULL") 类查询时可能全表扫描。
调用 Delete 会自动更新 DeletedAt 而非物理删除
对已启用软删除的模型调用 db.Delete(&user),GORM 不会执行 DELETE FROM,而是执行 UPDATE ... SET deleted_at = NOW()。前提是该记录尚未被软删除(即 DeletedAt 为 nil)。
- 如果
user.ID = 123且当前DeletedAt为 nil,db.Delete(&user)将设置时间戳 - 如果
DeletedAt已有值,再次Delete不会报错,但也不会重复更新(GORM 内部会跳过) - 传入主键而非结构体也可触发软删除:
db.Delete(&User{}, 123)
物理删除需显式使用 Unscoped():
db.Unscoped().Delete(&User{}, 123) // 真删
默认查询自动忽略软删除记录,但 Unscoped 可绕过
GORM 在所有常规查询(First、Find、Where 等)中自动添加 WHERE deleted_at IS NULL 条件。这意味着你几乎不需要手动过滤,但也容易因此查不到“已被删”的数据。
常见误操作:
-
db.Where("email = ?", "a@b.c").Find(&users)→ 查不到已软删除的邮箱 -
db.First(&user, "email = ?", "a@b.c")→ 即使数据库里存在该邮箱但已被软删除,也会返回record not found
需要包含软删除记录时,必须加 Unscoped():
db.Unscoped().Where("email = ?", "a@b.c").Find(&users) // 包含已删
注意:Unscoped 是链式方法,会影响后续所有条件,建议紧邻 db 调用,避免污染其他查询。
硬删除后无法恢复,且 DeletedAt 字段不会被清空
执行 Unscoped().Delete() 后,记录从数据库彻底消失,DeletedAt 字段值本身也随行一并消失——它不是“标记位”,而是软删除机制的载体。所以别指望靠把 DeletedAt 设为 nil 来“恢复”一条已被物理删除的记录。
想实现可逆软删除,得自己维护状态,比如:
- 加一个
Status字段("active"/"deleted"),不依赖 GORM 软删除 - 或在软删除前先备份关键字段到另一张表
GORM 的软删除本质只是“带时间戳的隐藏”,不是回收站。时间戳一旦写入,就只代表“何时被隐藏”,而不是“能否再显示”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











