1、定义
1.1 逻辑删除
不真正删除数据,而是通过一个标记字段(如is_deleted、deleted_at)表示该记录已被“删除”。查询时过滤掉这些记录。
1.1.1 常见实现
时间戳字段(推荐):
deleted_at DATETIME NULL(NULL 表示未删除)。既能表达状态,又能记录删除时间,还能顺带解决唯一索引问题布尔字段:
is_deleted TINYINT(1) DEFAULT 0。- 归档表:删除时把行搬到
xxx_history/xxx_archive,主表物理删。这是逻辑删除和物理删除的折中,主表保持精简,历史可追溯。
1.1.2 优点
数据可恢复,误删可找回
保留历史记录,便于审计、追溯
不影响关联数据(外键关系仍存在)
对业务代码侵入较小(只需在查询中加过滤条件)
1.1.3 缺点
表中数据量持续增长,影响查询性能
所有查询都要带过滤条件,容易遗漏
唯一索引与逻辑删除冲突(如用户名唯一,删除后无法复用)
需要额外存储空间
1.2 物理删除
直接从数据库中将记录移除,数据不可恢复。
1.2.1 常见实现
DELETE FROM users WHERE id = 100;1.2.2 优点
释放存储空间
表数据量小,查询性能好
无额外过滤条件,逻辑简单
唯一约束不会冲突
1.2.3 缺点
数据不可恢复,误删风险高
破坏历史记录,不利于审计
可能影响外键关联数据(需级联删除或置空)
高并发下可能产生锁竞争
2、对比
维度 | 物理删除(DELETE) | 逻辑删除(Soft Delete) |
|---|---|---|
数据留存 | 行被移除,仅 binlog/备份中可找回 | 行仍在表中,靠字段标记 |
恢复能力 | 难,需 binlog 闪回或从备份恢复 | 极易,改个字段值即可 |
查询成本 | 无额外开销 | 所有查询都要带 |
存储与性能 | 表体积可控 | 表持续膨胀,索引变大,冷热数据混在一起拖慢查询 |
唯一约束 | 天然可用 | 经典坑:删了的用户手机号/邮箱不能重新注册 |
外键关联 | 可能误删或触发级联 | 关联行依然存在,历史快照完整 |
合规 | 满足"被遗忘权"/数据最小化 | 数据仍在库里,未必满足删除要求 |
实现成本 | 零 | 需规范、ORM 拦截、归档机制配套 |
3、适用场景
3.1 推荐逻辑删除(软删)场景
- 需要数据可恢复:订单、客户账号、商品、文章、评论,误删可以撤回。
- 审计、对账、追溯:财务数据、业务流水,法规要求留存一段时间数据,不能直接删掉。
- 业务有回收站功能:网盘、后台管理系统,支持查看 / 恢复已删除记录。
- 业务存在删除后溯源需求:排查用户操作、纠纷取证。
缺点:表越来越大,查询索引效率慢慢下降,需要定期归档已软删数据到归档表。
3.2 推荐物理删除(硬删)场景
- 数据不需要任何恢复,无审计要求:临时日志、临时会话、缓存表、过期临时任务。
- 数据量极大,存储压力高:海量埋点日志、临时中间表,保留无用数据会拖慢查询。
- 隐私合规强制要求彻底清除:用户申请注销,法规要求彻底删除个人信息(GDPR / 个人信息保护法场景,单纯软删可能不满足合规)。
- 简单小型表,没有回滚需求,追求简单,不想每次查询都加过滤条件。
注意:很多场景用户注销只做逻辑删除不满足隐私合规,合规场景要物理删除或者脱敏后物理删除。
4、实践建议
成熟系统通常是两者结合:
默认逻辑删除,定期归档:业务表逻辑删除,后台定时任务把
deleted_at超过 30/90 天的记录物理迁移到归档表,再物理删除。处理唯一索引冲突:把唯一索引改为
(字段, deleted_at)复合唯一索引,删除时把deleted_at写成删除时间戳而不是固定值(如 1),这样同一个值删除多次也不冲突。谨慎选择标志位:用
deleted_at(时间戳,可知道何时删除)优于is_deleted(0/1)。查询别忘过滤:这是逻辑删除最大的坑——漏写条件就把"已删除"数据返回了。务必依赖 ORM 全局过滤或数据库视图,而不是靠人肉记得加
WHERE。级联问题:逻辑删除主表记录后,其关联的子表记录怎么办?要考虑是否也需要逻辑删除,否则会出现"孤儿数据"。
5、总结
有恢复/追溯需求的业务数据用逻辑删除,量大无价值或合规要求彻底抹除的数据用物理删除;两者结合 + 定期归档是大型系统的常见解法。