删除记录

删除数据看似简单,但涉及软删除、级联删除等概念,需要仔细理解。

基本删除

根据主键删除:

db.Delete(&User{}, 1)

删除模型实例:

user := User{ID: 1}
db.Delete(&user)

删除多条记录:

db.Delete(&User{}, []int{1, 2, 3})

条件删除

根据条件删除:

db.Where("age < ?", 18).Delete(&User{})
db.Where("name LIKE ?", "%测试%").Delete(&User{})

删除所有记录:

db.Where("1 = 1").Delete(&User{})

这很危险,生产环境要谨慎。

Delete 和 Unscoped

如果模型有 gorm.DeletedAt 字段,删除操作是软删除:

type User struct {
    ID        uint
    Name      string
    DeletedAt gorm.DeletedAt
}

db.Delete(&User{}, 1)

这不会真正删除记录,而是设置 deleted_at 字段。

要真正删除,用 Unscoped

db.Unscoped().Delete(&User{}, 1)

软删除

模型嵌入 gorm.DeletedAt 后自动启用软删除:

type User struct {
    gorm.Model
    Name string
}

gorm.Model 包含 IDCreatedAtUpdatedAtDeletedAt

软删除的记录不会出现在普通查询中:

db.Find(&users)

要查询包括软删除的记录:

db.Unscoped().Find(&users)

只查询软删除的记录:

db.Unscoped().Where("deleted_at IS NOT NULL").Find(&users)

批量删除

删除符合条件的所有记录:

db.Where("status = ?", "inactive").Delete(&User{})

获取删除数量:

result := db.Where("age < ?", 18).Delete(&User{})
fmt.Println(result.RowsAffected)

返回删除的记录

某些数据库支持返回删除的数据:

var users []User
db.Clauses(clause.Returning{}).Where("age < ?", 18).Delete(&users)

MySQL 不支持,PostgreSQL 和 SQL Server 支持。

级联删除

关联记录的删除行为由外键约束决定。在模型中定义:

type User struct {
    ID      uint
    Name    string
    Orders  []Order
}

type Order struct {
    ID     uint
    UserID uint
    User   User
}

删除用户时,订单的处理取决于数据库外键设置:

  • CASCADE - 同时删除订单
  • SET NULL - 订单的 user_id 设为 NULL
  • RESTRICT - 有订单时禁止删除用户

GORM 的关联标签可以指定:

type User struct {
    ID     uint
    Orders []Order `gorm:"constraint:OnDelete:CASCADE;"`
}

钩子函数

删除操作会触发钩子:

func (u *User) BeforeDelete(tx *gorm.DB) error {
    if u.Role == "admin" {
        return errors.New("不能删除管理员")
    }
    return nil
}

func (u *User) AfterDelete(tx *gorm.DB) error {
    log.Printf("用户 %s 已删除", u.Name)
    return nil
}

Unscoped().Delete() 也会触发钩子。

物理删除 vs 软删除

物理删除

真正从数据库移除记录:

  • 数据彻底消失
  • 释放存储空间
  • 无法恢复

软删除

标记记录为已删除:

  • 数据仍在数据库
  • 可以恢复
  • 需要额外字段

选择建议:

  • 敏感数据(密码、token)用物理删除
  • 业务数据用软删除,支持审计和恢复
  • 大数据量场景考虑物理删除,避免表膨胀

恢复软删除

恢复被软删除的记录:

db.Unscoped().Model(&User{}).Where("id = ?", 1).Update("deleted_at", nil)

常见错误

外键约束

ERROR: update or delete on table "users" violates foreign key constraint

有关联记录时无法删除。解决方案:

  • 先删除关联记录
  • 使用级联删除
  • 设置外键为 SET NULL

记录不存在

删除不存在的记录不会报错,RowsAffected 为 0:

result := db.Delete(&User{}, 999)
fmt.Println(result.RowsAffected)

忘记条件

db.Delete(&User{})

这会删除所有记录(软删除)。生产环境务必加条件。

小结

删除操作要区分软删除和物理删除。软删除适合业务数据,物理删除适合敏感数据。无论哪种,都要注意条件限制,避免误删。