物理删除必须用Unscoped(),它是绕过软删除拦截层的强制通行证;正确写法是db.Unscoped().Where("id = ?", 123).Delete(&User{}),且须确保主键非零值、批量操作必须带Where条件。

物理删除必须用 Unscoped()
GORM 默认所有 Delete() 都走软删除逻辑,想真正从数据库里抹掉记录,唯一可靠方式是前置 Unscoped()。它不是可选开关,而是绕过软删除拦截层的强制通行证。没加它,哪怕你删的是刚建的记录、字段也全对,照样只更新 deleted_at,不会执行 DELETE FROM 语句。
Unscoped() 必须放在链式调用最前面
顺序错了就失效。比如 db.Where("id = ?", 123).Unscoped().Delete(&User{}) 是错的——Where 已经在软删过滤后执行了,可能根本查不到那条记录;正确写法是 db.Unscoped().Where("id = ?", 123).Delete(&User{})。它影响整条链,包括后续的 Preload,这点在带关联查询的场景里特别容易翻车。
单条物理删除别传零值主键
用 db.Unscoped().Delete(&user) 前,确保 user.ID 不是 0 或空字符串。否则 GORM 可能生成无条件 DELETE FROM users,清空整张表。安全做法是显式传 ID:db.Unscoped().Delete(&User{}, 123),或先 First() 查出再删。
批量物理删除要加明确 Where 条件
直接 db.Unscoped().Delete(&User{}) 是高危操作,等同于 TRUNCATE TABLE(取决于驱动)。必须配合 Where:db.Unscoped().Where("status = ?", "draft").Delete(&User{})。日志里看到 DELETE FROM users WHERE status = 'draft' 才算到位;如果出现没 WHERE 的 DELETE,立刻停手检查。
立即学习“go语言免费学习笔记(深入)”;
最容易被忽略的是:软删除机制生效与否,不只看有没有 DeletedAt 字段,更取决于它的类型和嵌入方式;而 Unscoped() 的透传性会让整个链路(含关联预加载)都跳过过滤——这点在审计或回收站接口里,稍不注意就会漏数据或误删。


















