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.