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
gotype User struct { gorm.Model Name string Email string } // gorm.Model includes the DeletedAt gorm.DeletedAt field
Manually Define Soft Delete Field
gotype 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
gotype DeletedAt struct { Time time.Time Valid bool }
Time: Deletion timeValid: 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
govar 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
gotype 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
gotype 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
gotype 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
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 }
Best Practices for Soft Delete
1. Data Auditing
gotype 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
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. Periodic Cleanup
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 }
Notes
- Unique Index: Soft delete fields affect unique index constraints
- Performance Impact: Soft delete adds query conditions, which may affect performance
- Data Cleanup: Need to periodically clean up old soft-deleted data
- Association Queries: Pay attention to soft delete behavior in association queries
- Storage Space: Soft delete takes up additional storage space
- 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.