乐闻世界logo
搜索文章和话题

GORM 中的软删除(Soft Delete)是如何工作的?

3月6日 21:37

GORM 支持软删除(Soft Delete)功能,允许在逻辑上删除记录而不真正从数据库中删除它们。

软删除基本概念

软删除通过在模型中添加 DeletedAt 字段来实现,当执行删除操作时,GORM 不会真正删除记录,而是将 DeletedAt 字段设置为当前时间。

基本用法

启用软删除

go
type User struct { gorm.Model Name string Email string } // gorm.Model 包含了 DeletedAt gorm.DeletedAt 字段

手动定义软删除字段

go
type User struct { ID uint `gorm:"primaryKey"` Name string DeletedAt gorm.DeletedAt `gorm:"index"` }

软删除操作

删除记录

go
// 软删除 db.Delete(&user) // 批量软删除 db.Where("age < ?", 18).Delete(&User{}) // 根据主键软删除 db.Delete(&User{}, 1)

查询记录

默认情况下,GORM 会自动过滤掉已软删除的记录:

go
// 不会查询到已软删除的记录 var users []User db.Find(&users) // 只查询已软删除的记录 db.Unscoped().Find(&users) // 查询所有记录(包括已软删除的) db.Unscoped().Find(&users)

软删除的工作原理

DeletedAt 字段

go
type DeletedAt struct { Time time.Time Valid bool }
  • Time: 删除时间
  • Valid: 是否已删除(true 表示已删除)

SQL 生成

go
// 删除操作生成的 SQL // UPDATE users SET deleted_at = '2024-01-01 12:00:00' WHERE id = 1 // 查询操作生成的 SQL(自动添加条件) // SELECT * FROM users WHERE deleted_at IS NULL

高级用法

查找已软删除的记录

go
// 使用 Unscoped var deletedUsers []User db.Unscoped().Where("deleted_at IS NOT NULL").Find(&deletedUsers) // 或者直接查询 db.Unscoped().Find(&deletedUsers)

恢复已软删除的记录

go
// 恢复单个记录 var user User db.Unscoped().First(&user, 1) db.Model(&user).Update("DeletedAt", nil) // 批量恢复 db.Unscoped().Model(&User{}).Where("deleted_at IS NOT NULL").Update("DeletedAt", nil)

永久删除记录

go
// 永久删除(真正从数据库中删除) db.Unscoped().Delete(&user) // 批量永久删除 db.Unscoped().Where("age < ?", 18).Delete(&User{})

检查记录是否被软删除

go
var user User db.First(&user, 1) if user.DeletedAt.Valid { fmt.Println("记录已被软删除") } else { fmt.Println("记录未被删除") }

软删除与关联关系

关联查询中的软删除

go
type User struct { gorm.Model Name string Posts []Post } type Post struct { gorm.Model Title string UserID uint } // 查询用户时,不会包含已软删除的文章 var user User db.Preload("Posts").First(&user, 1) // 查询用户时,包含已软删除的文章 db.Preload("Posts", "deleted_at IS NOT NULL").Unscoped().First(&user, 1)

级联软删除

go
type User struct { gorm.Model Name string Posts []Post `gorm:"constraint:OnDelete:SET NULL"` } // 删除用户时,相关文章的 UserID 会被设置为 NULL db.Delete(&user)

自定义软删除

自定义软删除字段名

go
type User struct { ID uint `gorm:"primaryKey"` Name string DeletedAt time.Time `gorm:"index"` IsDeleted bool `gorm:"default:false"` } // 自定义软删除逻辑 func (u *User) BeforeDelete(tx *gorm.DB) error { u.IsDeleted = true return nil }

使用不同的软删除策略

go
type User struct { ID uint `gorm:"primaryKey"` Name string Status string `gorm:"default:'active'"` } func (u *User) BeforeDelete(tx *gorm.DB) error { u.Status = "deleted" return tx.Save(u).Error }

软删除的最佳实践

1. 数据审计

go
type User struct { gorm.Model Name string DeletedBy uint } func (u *User) BeforeDelete(tx *gorm.DB) error { // 记录删除操作者 u.DeletedBy = getCurrentUserID() return nil }

2. 数据恢复

go
func RestoreUser(db *gorm.DB, userID uint) error { return db.Transaction(func(tx *gorm.DB) error { var user User if err := tx.Unscoped().First(&user, userID).Error; err != nil { return err } return tx.Model(&user).Update("DeletedAt", nil).Error }) }

3. 定期清理

go
func CleanOldDeletedRecords(db *gorm.DB, days int) error { threshold := time.Now().AddDate(0, 0, -days) return db.Unscoped(). Where("deleted_at < ?", threshold). Delete(&User{}).Error }

注意事项

  1. 唯一索引:软删除的字段会影响唯一索引的约束
  2. 性能影响:软删除会增加查询条件,可能影响性能
  3. 数据清理:需要定期清理已软删除的旧数据
  4. 关联查询:注意关联查询中的软删除行为
  5. 存储空间:软删除会占用额外的存储空间
  6. 业务逻辑:确保业务逻辑正确处理软删除的记录

常见问题

Q: 软删除会影响唯一索引吗?

A: 会,因为软删除的记录仍然存在于数据库中,可能会违反唯一索引约束。可以使用复合唯一索引来解决。

Q: 如何批量恢复已软删除的记录?

A: 使用 Unscoped()Update() 方法将 DeletedAt 设置为 nil

Q: 软删除和硬删除有什么区别?

A: 软删除只是标记记录为已删除,数据仍然存在;硬删除会真正从数据库中删除记录。

Q: 如何查询特定时间范围内被删除的记录?

A: 使用 Unscoped() 和时间范围查询条件。

标签:Gorm