GORM 支持软删除(Soft Delete)功能,允许在逻辑上删除记录而不真正从数据库中删除它们。
软删除基本概念
软删除通过在模型中添加 DeletedAt 字段来实现,当执行删除操作时,GORM 不会真正删除记录,而是将 DeletedAt 字段设置为当前时间。
基本用法
启用软删除
gotype User struct { gorm.Model Name string Email string } // gorm.Model 包含了 DeletedAt gorm.DeletedAt 字段
手动定义软删除字段
gotype 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 字段
gotype 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{})
检查记录是否被软删除
govar user User db.First(&user, 1) if user.DeletedAt.Valid { fmt.Println("记录已被软删除") } else { fmt.Println("记录未被删除") }
软删除与关联关系
关联查询中的软删除
gotype 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)
级联软删除
gotype User struct { gorm.Model Name string Posts []Post `gorm:"constraint:OnDelete:SET NULL"` } // 删除用户时,相关文章的 UserID 会被设置为 NULL db.Delete(&user)
自定义软删除
自定义软删除字段名
gotype 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 }
使用不同的软删除策略
gotype 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. 数据审计
gotype User struct { gorm.Model Name string DeletedBy uint } func (u *User) BeforeDelete(tx *gorm.DB) error { // 记录删除操作者 u.DeletedBy = getCurrentUserID() return nil }
2. 数据恢复
gofunc 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. 定期清理
gofunc CleanOldDeletedRecords(db *gorm.DB, days int) error { threshold := time.Now().AddDate(0, 0, -days) return db.Unscoped(). Where("deleted_at < ?", threshold). Delete(&User{}).Error }
注意事项
- 唯一索引:软删除的字段会影响唯一索引的约束
- 性能影响:软删除会增加查询条件,可能影响性能
- 数据清理:需要定期清理已软删除的旧数据
- 关联查询:注意关联查询中的软删除行为
- 存储空间:软删除会占用额外的存储空间
- 业务逻辑:确保业务逻辑正确处理软删除的记录
常见问题
Q: 软删除会影响唯一索引吗?
A: 会,因为软删除的记录仍然存在于数据库中,可能会违反唯一索引约束。可以使用复合唯一索引来解决。
Q: 如何批量恢复已软删除的记录?
A: 使用 Unscoped() 和 Update() 方法将 DeletedAt 设置为 nil。
Q: 软删除和硬删除有什么区别?
A: 软删除只是标记记录为已删除,数据仍然存在;硬删除会真正从数据库中删除记录。
Q: 如何查询特定时间范围内被删除的记录?
A: 使用 Unscoped() 和时间范围查询条件。