gorm默认仅识别deletedat字段实现软删除;若用is_deleted或deleted_time等自定义名,须显式添加gorm:"softdelete"或gorm:"softdelete:flag"标签并确保类型匹配(*time.time或bool),否则delete将执行硬删除。

软删除字段名不是 DeletedAt 怎么办
GORM 默认只识别 DeletedAt 字段作为软删除标识,如果数据库表里用的是 is_deleted(布尔型)或 deleted_time(时间型),GORM 不会自动启用软删除逻辑,DELETE 会变成真删,Find 也不会自动过滤。
解决方式是显式配置模型的软删除字段,并确保类型兼容:
- 时间型自定义字段(如
deleted_at):必须是*time.Time类型,并在 struct tag 中声明gorm:"softDelete" - 布尔型字段(如
is_deleted):用gorm:"softDelete:flag",且字段类型必须是bool或*bool - 整数型标记(如
delete_status):GORM v1.25+ 支持gorm:"softDelete:unix"或自定义值,但需配合SoftDelete插件或手动处理,不推荐
示例:
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
DeletedAt *time.Time `gorm:"softDelete"` // 自定义时间字段名,仍需指针 time.Time
}
注意:DeletedAt 是 GORM 内部约定名,改名后必须加 tag,否则无效;且迁移时不会自动创建该字段,得自己加。
时间戳字段名和更新逻辑怎么自定义
GORM 默认使用 CreatedAt、UpdatedAt、DeletedAt,但很多项目用 created_time、updated_time 等命名。直接改 struct 字段名不管用,必须通过 tag 显式映射。
关键点:
-
CreatedAt和UpdatedAt字段名可以任意,但必须带gorm:"autoCreateTime"/gorm:"autoUpdateTime"tag - 若想让
UpdatedAt在软删除时也更新(比如记录“最后失效时间”),需额外加gorm:"autoUpdateTime:delete" - 所有时间戳字段类型建议统一为
*time.Time,避免零值写入数据库出错
示例:
type Product struct {
ID uint `gorm:"primaryKey"`
Title string `gorm:"not null"`
CreatedTime *time.Time `gorm:"autoCreateTime"`
UpdatedTime *time.Time `gorm:"autoUpdateTime;autoUpdateTime:delete"`
DeletedTime *time.Time `gorm:"softDelete"`
}
这里 UpdatedTime 会在 INSERT、UPDATE、SOFT DELETE 三种操作时都刷新 —— 这是容易被忽略的默认行为,不加 autoUpdateTime:delete 就不会在软删时更新。
软删除 + 自定义时间戳组合后,查询/恢复常见陷阱
一旦改了字段名或用了布尔型软删,Unscoped() 依然有效,但 Unscopede().Where("is_deleted = ?", false) 这类手写条件可能绕过 GORM 的软删机制,导致误删或漏查。
典型问题:
-
First()或Find()查不到已软删数据 → 正常,GORM 自动加了WHERE deleted_at IS NULL(或对应布尔条件) -
Unscoped().Delete(&u)执行的是硬删 → 如果字段是布尔型,它会把is_deleted设为true,不是物理删;只有时间型字段 +Unscoped()+Delete()才真删 - 想“恢复”软删数据,不能只改字段值,得用
Unscoped().Model(&u).Update("deleted_at", nil)(时间型)或Unscoped().Model(&u).Update("is_deleted", false)(布尔型)
特别注意:Update() 不触发 UpdatedAt 自动更新,除非你显式调用 Save() 或在 tag 里加 autoUpdateTime 并确保字段非零值。
为什么 gorm.Model(&u).Unscoped().Delete(&u) 有时没效果
这个写法看似标准,但实际取决于模型是否含主键、软删字段是否被识别、以及 GORM 版本。v1.24+ 后,Delete() 在 Unscoped 模式下对布尔型软删字段只是设值,不会执行 SQL DELETE FROM;而对时间型字段,它会把 deleted_at 设为 NULL,等价于“恢复”,不是硬删。
真正硬删(绕过所有软删逻辑)的唯一可靠方式是:
- 用原生 SQL:
db.Exec("DELETE FROM users WHERE id = ?", u.ID) - 或先
Unscoped().Select("id").Where("id = ?", u.ID).Delete(&User{})(仅限 GORM v2,且要确认没其他钩子干扰)
更隐蔽的问题是:如果自定义软删字段没加 softDelete tag,GORM 完全不感知,Delete() 就是真删 —— 这时候你以为在软删,其实数据没了。











