返回
创建于
状态
公开

这是一个非常实用且具有争议性的问题!软删除(Soft Delete)几乎在中大型项目中都会用到,但实现方式不当,会影响性能、数据一致性甚至开发体验。


✅ 一、什么是软删除?

软删除就是不真正删除数据库中的记录,而是用某个字段标记为“已删除”,例如:

sql
1ALTER TABLE users ADD COLUMN is_deleted TINYINT(1) DEFAULT 0;

is_deleted = 1,就代表该记录被逻辑删除,但数据仍然保留在数据库中。


🚀 二、软删除的最佳实践(通用策略)

我们可以从 字段设计、索引策略、代码约束、数据清理、架构演化 五个方面来讲:


✅ 1. 字段设计:推荐使用时间戳字段,而非布尔字段

比起 is_deleted TINYINT(1),更推荐:

sql
1deleted_at DATETIME DEFAULT NULL

优势:

  • 记录删除时间,便于审计、恢复。
  • 判断是否删除只需 WHERE deleted_at IS NULL
  • 可顺便用 deleted_at 加索引,对“已删除数据”快速检索。

✅ 2. 索引策略

🚫 不推荐:

sql
1CREATE INDEX idx_is_deleted ON users(is_deleted);

理由见上面:选择性差,回表多,影响写入性能。

✅ 推荐:

  • 不要给 deleted_at 单独建索引。
  • 如果查询条件包含其他字段,应该使用联合索引:
sql
1CREATE INDEX idx_status_deleted ON users(status, deleted_at);
  • 针对被删数据查询(如后台审计)可用:
sql
1CREATE INDEX idx_deleted_at_only ON users(deleted_at)
2WHERE deleted_at IS NOT NULL;  -- 条件索引(PostgreSQL 支持,MySQL 8.0 部分支持)

✅ 3. ORM层统一处理(框架级封装)

比如在 Laravel、Django、SQLAlchemy 中,可以使用框架自带的软删除模型:

  • 查询时自动加 WHERE deleted_at IS NULL
  • 删除时自动设置 deleted_at = NOW()
  • 还支持 .restore() 撤销删除

这样你不会忘记加判断,防止误查到已删除的数据。


✅ 4. 数据清理策略(定期物理删除)

软删除只是临时保留数据,不等于“永不删除”。建议定期清理“过期的软删数据”,比如:

sql
1DELETE FROM users WHERE deleted_at < NOW() - INTERVAL 90 DAY;

可以使用后台任务、定时器(如 cron)等方式批量清除老旧记录。


✅ 5. 架构演进建议(支持归档、版本控制)

对于历史数据量特别大的系统:

  • 可以将软删除数据迁移到归档表(如 users_archive);
  • 或使用历史版本表来记录所有改动(审计/合规);
  • 或存入数据湖(如 S3)中,节省主库空间。

💥 三、软删除的坑及规避方式

问题建议
忘记加 WHERE deleted_at IS NULLORM封装或视图封装,防止误查
查询性能变差不给低选择性字段单独建索引;考虑联合索引
表越来越大加入定期物理清理机制
误删数据难恢复使用 deleted_at 而非 is_deleted,便于恢复
写操作变慢删除数据多时,索引开销增加,及时归档

🧪 四、进阶技巧

🔸 使用视图封装“有效数据”

sql
1CREATE VIEW active_users AS
2SELECT * FROM users WHERE deleted_at IS NULL;

避免开发人员误写 SQL 忽略软删除条件。


🔸 写触发器记录操作日志

sql
1CREATE TRIGGER log_delete BEFORE UPDATE ON users
2FOR EACH ROW
3WHEN NEW.deleted_at IS NOT NULL AND OLD.deleted_at IS NULL
4BEGIN
5  INSERT INTO audit_log(user_id, deleted_time) VALUES (OLD.id, NOW());
6END;

✅ 五、最佳实践总结

项目建议
字段使用 deleted_at DATETIME
索引联合索引 + 部分索引(而非单字段布尔索引)
ORM支持利用框架封装删除逻辑
清理机制定期批量删除旧记录
安全性支持数据恢复、审计日志