gorm的级联删除需数据库外键on delete cascade与struct tag中constraint:ondelete:cascade配合才生效,二者缺一不可;默认delete()仅软删主表,不触发任何级联,硬删须unscoped()且外键已正确配置。

什么是GORM的级联删除,它真的自动生效吗
GORM默认不会自动执行级联删除,哪怕你在数据库里建了外键并加了 ON DELETE CASCADE,GORM的 Delete() 方法默认仍只删主表记录。这是新手最容易误判的一点:以为加了外键约束,调用 db.Delete(&user) 就会连带删掉它的 Orders,实际并不会。
真正起作用的是GORM的两个机制:
- 数据库外键约束(需手动在migration或SQL中显式定义)
- GORM的 struct tag 控制(
foreignKey、constraint等)
两者要配合使用,缺一不可。只设tag不建外键,数据库层不执行级联;只建外键不配GORM tag,GORM可能因预加载或关联字段缺失报错或跳过关联操作。
怎么用 struct tag 声明级联行为
GORM v2(即 gorm.io/gorm)通过 gorm tag 的 constraint 选项控制级联逻辑,不是靠 cascade 这种旧写法。
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
Orders []Order `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
<p>type Order struct {
ID uint <code>gorm:"primaryKey"</code>
Title string <code>gorm:"not null"</code>
UserID uint <code>gorm:"index"</code>
}
</p>
关键点:
-
constraint:OnDelete:CASCADE是告诉GORM:“删User时,也删对应Order”,但前提是数据库外键已启用该行为 -
foreignKey:UserID必须与子表字段名完全一致,大小写敏感 - 如果子表字段是
user_id(snake_case),tag里也得写foreignKey:user_id,不能写UserID -
constraint只在 AutoMigrate 时生成带级联的外键,如果表已存在,AutoMigrate不会修改已有外键,必须手动ALTER TABLE
为什么 AutoMigrate 没生成 ON DELETE CASCADE
常见原因有三个:
- 表已存在,且原外键没
ON DELETE CASCADE,AutoMigrate默认跳过外键重建 - struct tag 写错了,比如漏了分号、拼错
OnDelete或用了旧版GORM v1的cascadetag - 使用了
db.Migrator().CreateConstraint()但没指定具体约束名,GORM无法定位到哪条外键
安全做法是显式重建外键:
db.Migrator().DropConstraint(&Order{}, "fk_orders_user_id")
db.Migrator().CreateConstraint(&Order{}, "fk_orders_user_id")
注意:约束名格式为 fk_{子表名}_{外键字段名},GORM v2默认如此,不能随意改。
硬删除 vs 软删除对级联的影响
GORM默认开启软删除(DeletedAt 字段),而 constraint:OnDelete:CASCADE 只对硬删除(Unscoped().Delete())生效。普通 Delete() 只会更新主表的 DeletedAt,子表记录完全不动。
如果你需要“软删除级联”,只能手动处理:
tx := db.Begin()
if err := tx.Unscoped().Where("user_id = ?", userID).Delete(&Order{}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Unscoped().Delete(&User{}, userID).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
别依赖GORM自动做软级联——它不支持。
外键级联和GORM tag只是起点,实际项目中常要混合硬删、软删、归档逻辑,这时候级联容易变成黑盒陷阱。最稳妥的方式,始终检查生成的SQL(开 logger.Default.LogMode(logger.Info)),确认删了哪些表、是否真发出了 DELETE FROM orders WHERE user_id = ?。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











