gorm软删除需模型含*time.time类型的deletedat字段并嵌入gorm.model或加gorm:"index"标签,否则delete()执行物理删除;默认查询自动过滤软删记录,查全部或恢复须用unscoped()。

软删除不是“物理删除”,Gin 本身不参与删除逻辑,真正决定是软删还是物理删的,只有 GORM 的模型定义和调用方式。只要模型含 DeletedAt 字段且类型正确,Delete() 默认就是软删;想物理删,必须显式加 Unscoped()。
为什么 Delete() 没执行 DELETE SQL?
这是 GORM 软删除机制的正常表现,不是 Gin 或数据库配置问题。关键看模型有没有生效的软删除字段:
-
DeletedAt字段名必须首字母大写,类型必须是*time.Time(指针),不能是time.Time或sql.NullTime - 推荐直接嵌入
gorm.Model,它已内置带索引的DeletedAt *time.Time - 若自定义字段,必须写成
DeletedAt gorm.DeletedAt `gorm:"index"`,gorm:"index"不可省——否则 WHERE 查询可能全表扫描 - 如果字段名写成
IsDeleted或类型用错,Delete()会退化为物理删除,且不报错
如何触发物理删除(跳过软删)?
物理删除必须绕过 GORM 的软删除拦截层,仅靠 Unscoped() 不够,还要注意链式调用位置和作用域:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
db.Unscoped().Delete(&user)→ 执行DELETE FROM,且不触发BeforeDelete钩子 -
db.Unscoped().Where("id = ?", 123).Delete(&User{})→ 安全的批量物理删,不依赖实例主键 - 错误写法:
db.Delete(&user).Unscoped()——Unscoped()必须在Delete()前,否则无效 - 事务中物理删后,别忘了
Commit(),否则回滚时软删状态可能残留
查不到软删数据?这不是 bug
GORM 默认所有查询(Find、First、Count)都会自动加 WHERE deleted_at IS NULL。你“查不到”,恰恰说明软删生效了:
- 查存活数据:直接
db.Where("name = ?", "foo").Find(&users)✅ - 查全部记录(含软删):
db.Unscoped().Find(&users)✅ - 只查已软删的:
db.Unscoped().Where("deleted_at IS NOT NULL").Find(&users)✅ - 用
Raw()查时,GORM 完全不干预,必须手动补AND deleted_at IS NULL,否则会漏过滤
恢复软删记录的唯一安全方式
不能用 Save() 或普通 Update(),必须用 Unscoped() 显式清除 DeletedAt:
-
db.Unscoped().Model(&user).Update("deleted_at", nil)—— 注意是nil,不是time.Time{}或空字符串 - 若用
db.Model(&user).Updates(map[string]interface{}{"deleted_at": nil}),没加Unscoped()会被忽略 -
Preload关联查询时,Unscoped()会透传到关联表——比如恢复用户时也顺带恢复其软删的订单,这点极易被忽略
最常踩的坑不是不会写代码,而是字段类型写错导致新记录一创建就查不到,或是 Unscoped() 放错位置让物理删失效。软删除机制高度依赖字段声明的精确性,少一个星号(*)、少一个标签(gorm:"index"),行为就完全不同。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










