级联删除不生效的根本原因是未显式启用数据库级外键cascade或未正确调用association().delete();gorm默认不自动清理关联数据,save()等操作对中间表无影响。

级联删除不生效?先确认你用的是数据库级还是应用级
GORM 默认根本不会自动删关联数据,所谓“级联”必须显式启用——而且只有两种可靠路径:要么靠数据库外键的 ON DELETE CASCADE,要么手动调用 Association().Delete()。别指望 Save()、Update() 或结构体切片修改能触发任何关联清理,它们对此完全无感。
想物理删除关联记录,必须在数据库建外键时指定 CASCADE
这是唯一能保证原子性、一致性、并发安全的方式。GORM 的 AddForeignKey() 是推荐入口,但要注意参数顺序和命名约定:
-
db.Model(&Employee{}).AddForeignKey("company_id", "companies(id)", "CASCADE", "NO ACTION")—— 第一个参数是当前表的外键列名(不是结构体字段名),第二个是目标表主键引用格式(小写复数表名 +(id)) - 如果表名不是 GORM 默认复数(比如叫
company_profiles),就得手动指定:"company_profiles(id)" - 执行该操作前,确保
company_id字段已存在且类型匹配,否则报错或静默失败 - 迁移脚本里加比运行时加更稳妥;线上环境补加需评估锁表风险
多对多关系中删中间表行,别碰 Save(),直接用 Association().Delete()
例如用户解绑某个角色,只删 user_roles 里一行,不删 roles 表本身:
-
db.Model(&user).Association("Roles").Delete(&role)—— 参数必须是已有主键的实例或指针 -
db.Model(&user).Association("Roles").Delete(&Role{ID: 5})—— 更轻量,不依赖预加载 - 传
[]uint{1,2}也能批量删,但注意类型必须匹配主键类型(uint/int/string) - 别用
user.Roles = user.Roles[:i] + user.Roles[i+1:]再Save(),这不会动中间表一毫
软删除模型下,Cascade 不会绕过 DeletedAt
如果模型嵌入了 gorm.Model 或定义了 DeletedAt 字段,那么即使外键设了 CASCADE,数据库级级联也只会更新 DeletedAt,不会物理删除。真正物理删关联数据,必须:
- 对外键约束单独建不含软删除语义的子表(如
user_roles_hard),并配独立外键 - 或放弃数据库级级联,改用事务包裹手动
Delete()+Unscoped().Delete() - 别依赖
Unscoped().Delete(&parent)去触发关联的物理删除——它只影响主表,不改变外键行为











