3月6日 21:37

How does Soft Delete work in GORM?

GORM supports Soft Delete functionality, allowing records to be logically deleted without actually removing them from the database.

Basic Concept of Soft Delete

Soft delete is implemented by adding a DeletedAt field to the model. When a delete operation is performed, GORM does not actually delete the record but sets the DeletedAt field to the current time.

Basic Usage

Enable Soft Delete

go
type User struct { gorm.Model Name string Email string } // gorm.Model includes the DeletedAt gorm.DeletedAt field

Manually Define Soft Delete Field

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

Soft Delete Operations

Delete Records

go
// Soft delete db.Delete(&user) // Batch soft delete db.Where("age < ?", 18).Delete(&User{}) // Soft delete by primary key db.Delete(&User{}, 1)

Query Records

By default, GORM automatically filters out soft-deleted records:

go
// Will not query soft-deleted records var users []User db.Find(&users) // Only query soft-deleted records db.Unscoped().Find(&users) // Query all records (including soft-deleted) db.Unscoped().Find(&users)

How Soft Delete Works

DeletedAt Field

go
type DeletedAt struct { Time time.Time Valid bool }
  • Time: Deletion time
  • Valid: Whether deleted (true means deleted)

SQL Generation

go
// SQL generated for delete operation // UPDATE users SET deleted_at = '2024-01-01 12:00:00' WHERE id = 1 // SQL generated for query operation (automatically adds condition) // SELECT * FROM users WHERE deleted_at IS NULL

Advanced Usage

Find Soft-Deleted Records

go
// Use Unscoped var deletedUsers []User db.Unscoped().Where("deleted_at IS NOT NULL").Find(&deletedUsers) // Or directly query db.Unscoped().Find(&deletedUsers)

Restore Soft-Deleted Records

go
// Restore single record var user User db.Unscoped().First(&user, 1) db.Model(&user).Update("DeletedAt", nil) // Batch restore db.Unscoped().Model(&User{}).Where("deleted_at IS NOT NULL").Update("DeletedAt", nil)

Permanently Delete Records

go
// Permanently delete (actually delete from database) db.Unscoped().Delete(&user) // Batch permanent delete db.Unscoped().Where("age < ?", 18).Delete(&User{})

Check if Record is Soft-Deleted

go
var user User db.First(&user, 1) if user.DeletedAt.Valid { fmt.Println("Record has been soft deleted") } else { fmt.Println("Record is not deleted") }

Soft Delete and Associations

Soft Delete in Association Queries

go
type User struct { gorm.Model Name string Posts []Post } type Post struct { gorm.Model Title string UserID uint } // When querying user, soft-deleted posts are not included var user User db.Preload("Posts").First(&user, 1) // When querying user, include soft-deleted posts db.Preload("Posts", "deleted_at IS NOT NULL").Unscoped().First(&user, 1)

Cascade Soft Delete

go
type User struct { gorm.Model Name string Posts []Post `gorm:"constraint:OnDelete:SET NULL"` } // When deleting user, related posts' UserID will be set to NULL db.Delete(&user)

Custom Soft Delete

Custom Soft Delete Field Name

go
type User struct { ID uint `gorm:"primaryKey"` Name string DeletedAt time.Time `gorm:"index"` IsDeleted bool `gorm:"default:false"` } // Custom soft delete logic func (u *User) BeforeDelete(tx *gorm.DB) error { u.IsDeleted = true return nil }

Use Different Soft Delete Strategy

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 }

Best Practices for Soft Delete

1. Data Auditing

go
type User struct { gorm.Model Name string DeletedBy uint } func (u *User) BeforeDelete(tx *gorm.DB) error { // Record who deleted the record u.DeletedBy = getCurrentUserID() return nil }

2. Data Recovery

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. Periodic Cleanup

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 }

Notes

  1. Unique Index: Soft delete fields affect unique index constraints
  2. Performance Impact: Soft delete adds query conditions, which may affect performance
  3. Data Cleanup: Need to periodically clean up old soft-deleted data
  4. Association Queries: Pay attention to soft delete behavior in association queries
  5. Storage Space: Soft delete takes up additional storage space
  6. Business Logic: Ensure business logic correctly handles soft-deleted records

Common Questions

Q: Does soft delete affect unique indexes?

A: Yes, because soft-deleted records still exist in the database and may violate unique index constraints. You can use composite unique indexes to solve this.

Q: How to batch restore soft-deleted records?

A: Use Unscoped() and Update() methods to set DeletedAt to nil.

Q: What's the difference between soft delete and hard delete?

A: Soft delete only marks records as deleted, data still exists; hard delete actually removes records from the database.

Q: How to query records deleted within a specific time range?

A: Use Unscoped() and time range query conditions.

标签:Gorm