逻辑删除本质是update操作,需添加is_deleted字段并更新其值;物理删除用delete或truncate,前者可回滚、触发binlog,后者不可回滚、重置自增id且权限要求更高。

逻辑删除不能靠 DELETE 语句直接实现,它本质是 UPDATE 操作;物理删除才用 DELETE 或 TRUNCATE,但两者行为和后果完全不同。
逻辑删除必须加字段并用 UPDATE 模拟
真实业务中“删了还能恢复”,靠的是在表里加一个状态字段(比如 is_deleted TINYINT(1) DEFAULT 0),然后把“删除”动作转为更新该字段值。
-
UPDATE user SET is_deleted = 1 WHERE id = 123;—— 这才是逻辑删除的标准写法 - 后续所有查询必须显式过滤:
SELECT * FROM user WHERE is_deleted = 0;,否则会查出“已删”数据 - 不能依赖
WHERE默认不查被删数据:应用层、视图、甚至索引都要同步适配该字段 - 如果原表没这个字段,
ALTER TABLE user ADD COLUMN is_deleted TINYINT(1) DEFAULT 0;是前置必要操作
DELETE 和 TRUNCATE 都是物理删除,但风险点不同
DELETE FROM user WHERE id = 123; 看似可控,但它仍会真实释放行记录、触发 binlog、走事务机制;而 TRUNCATE TABLE user; 更激进,直接重建空表,不可回滚、不走事务、重置自增 ID。
-
DELETE不加WHERE条件 = 全表逐行删除,InnoDB 下仍保留自增值,磁盘空间不一定立刻回收 -
TRUNCATE无法带条件,也不支持事务回滚,执行后原表结构保留但数据全失,且权限要求更高(需DROP权) - 两者都会清空
INFORMATION_SCHEMA.TABLES中的DATA_LENGTH统计,但DELETE后可能需要OPTIMIZE TABLE才能真正回收空间
切换策略的关键在于查询与写入是否统一走同一套判断逻辑
很多团队踩坑不是因为不会写 UPDATE is_deleted=1,而是后续忘了改所有 SELECT、JOIN、COUNT(*) 场景——它们默认都无视逻辑删除标记。
- 建议建视图封装常用查询:
CREATE VIEW user_active AS SELECT * FROM user WHERE is_deleted = 0; - 避免在应用代码里到处写
WHERE is_deleted = 0,容易漏;MyBatis-Plus 的@TableLogic注解或 Hibernate 的 @Where 就是为解决这个问题 - 如果某天要“转物理删除”,别直接
DELETE FROM user WHERE is_deleted = 1,先确认无外键引用、无归档需求、binlog 已备份 - 物理删除前,建议先用
SELECT COUNT(*) FROM user WHERE is_deleted = 1;评估影响范围
最常被忽略的一点:逻辑删除字段一旦上线,就再也不能被当作普通业务字段使用——比如拿 is_deleted 做分库分表路由、做唯一索引的一部分,或者在 ON DUPLICATE KEY UPDATE 里参与冲突判断,都可能引发意料外的行为。











