GORM事务中Delete触发软删除(UPDATE DeletedAt),但需确保模型含正确声明的DeletedAt字段、避免Unscoped绕过、手动处理关联软删,并验证回滚一致性;否则易导致物理删除或状态残留。

事务中调用 Delete 会触发软删除,但不等于“逻辑上安全”
软删除在 GORM 中本质是 UPDATE 而非 DELETE,所以它天然能参与事务。但问题常出在「你以为删了,其实没删干净」——比如关联数据未同步软删、钩子未执行、或事务回滚后状态不一致。
关键点:GORM 的软删除依赖模型中定义的 DeletedAt 字段(类型必须为 *time.Time),且需启用 gorm.DeletedAt 标签;否则 Delete 会走物理删除,事务中直接丢数据。
- 确认结构体字段已正确声明:
DeletedAt gorm.DeletedAt `gorm:"index"` - 避免手动赋值
DeletedAt: &now—— GORM 会在Delete时自动设置 - 若事务中混用
Unscoped().Delete(),会绕过软删除逻辑,导致部分数据被物理删除
Transaction 内执行软删除时,关联数据不会自动软删
GORM 不会递归处理关联模型的软删除。例如 User 有多个 Order,对 User 调用 Delete,Order 表的 deleted_at 字段不会变,除非你显式处理。
- 手动批量更新关联表:
db.Where("user_id = ?", user.ID).Model(&Order{}).Update("deleted_at", time.Now()) - 或在
BeforeDelete钩子中统一处理,但注意钩子在事务上下文中执行,失败会导致整个事务回滚 - 慎用
CascadeDelete标签——它只控制物理删除行为,对软删除无效
事务回滚后,DeletedAt 字段可能残留脏数据
这是最容易被忽略的坑:事务中调用了 Delete,之后因错误调用 Rollback,但数据库里 deleted_at 已被写入,回滚却没把它清空。
原因在于 GORM 的软删除是普通 UPDATE,而事务回滚能撤销它——**前提是事务没提前提交或连接被复用**。常见误操作:
- 用
db.Session(&gorm.Session{NewDB: true})创建新 session 后忘设Context,导致实际不在同一事务中 - 在事务内调用了其他未传入该
*gorm.DB实例的数据库操作(比如另起一个gorm.Open连接) - 使用了
db.Exec或原生 SQL 更新deleted_at,绕过了 GORM 事务管理
测试软删除是否真在事务中生效,别只看返回值
Delete 返回 error 为 nil 只代表 SQL 执行成功,不代表业务语义正确。要验证,得查数据库状态 + 检查后续查询是否过滤掉该记录。
- 事务内删除后,立刻用
db.First(&u, user.ID)应该报record not found(默认作用域已过滤软删除) - 用
db.Unscoped().First(&u, user.ID)应能查到,且u.DeletedAt非 nil - 务必在事务
Commit前做这两步检查,否则上线后才发现软删没生效,补救成本极高
事务中软删除真正难的不是语法,而是边界控制:什么时候该查 Unscoped,什么时候不该;关联怎么同步;回滚后状态是否可逆。这些地方一松懈,数据一致性就悄悄破防。











