软删除字段应使用TINYINT(1)类型且默认值为0;UPDATE需加WHERE deleted = 0防重复,查询必须显式添加WHERE deleted = 0,deleted字段建议建单列索引。

软删除字段该用什么类型和默认值
MySQL 里实现软删除,deleted 字段最常用的是 TINYINT(1) 或 BOOLEAN(本质就是 TINYINT(1)),不是 VARCHAR 或 ENUM。
用整型判断快、索引友好、ORM 映射也省心;用字符串比如 'yes'/'no' 反而容易写错、查得慢、还占空间。
- 默认值必须设为
0(代表“未删除”),不能是NULL - 不要设成
DEFAULT NULL,否则查询时得写WHERE deleted IS NULL OR deleted = 0,漏条件就出事 - 如果业务需要区分“从未删除”和“已恢复”,才考虑加
deleted_at时间戳字段,否则单字段够用
UPDATE 语句怎么写才不会误删或漏改
软删除本质是 UPDATE,不是 DELETE。核心原则:永远带主键或唯一约束条件,且 WHERE 中必须显式过滤掉已删除数据(避免重复软删)。
- 错误写法:
UPDATE user SET deleted = 1 WHERE id = 123—— 没检查当前是否已是deleted = 1,重复执行无感知,但可能干扰审计逻辑 - 推荐写法:
UPDATE user SET deleted = 1 WHERE id = 123 AND deleted = 0 - 更安全的做法(尤其批量操作):先
SELECT id FROM user WHERE id IN (123, 456) AND deleted = 0,确认再更新 - 别在事务外直接执行软删语句,尤其是关联多表时——万一中间出错,状态可能不一致
查询时为什么老漏掉软删除的数据
几乎所有软删除 bug 都出在这里:开发写查询时忘了加 WHERE deleted = 0,导致前端看到“已删除”的数据,或者统计数偏高。
- 所有业务查询(包括 JOIN、子查询、COUNT)都得显式排除:
WHERE deleted = 0 - ORM 如 Laravel Eloquent 有软删除 Trait,会自动加这个条件;但原生 SQL、MyBatis XML、JDBC 手写语句全得自己加
- 容易被忽略的场景:
-
LEFT JOIN后没限制右表的deleted状态,导致关联出已删除记录 - 分页 COUNT(*) 忘了同步加
WHERE deleted = 0,总数对不上 - 后台导出 Excel 的 SQL 通常独立维护,最容易漏条件
-
deleted 字段要不要建索引
要,但得看查询模式。不加索引,软删除后所有 WHERE deleted = 0 查询都会走全表扫描。
- 单列索引足够:
ALTER TABLE user ADD INDEX idx_deleted (deleted) - 如果高频查询同时带
deleted和status或created_at,考虑联合索引,比如(deleted, status, created_at) - 注意:如果表里 95% 数据都是
deleted = 1,那deleted = 0是低选择性条件,索引效果有限,得结合实际执行计划看 - 别给
deleted加唯一索引——没意义,也不符合业务逻辑
软删除看着简单,真正难的是所有读写路径的一致性覆盖。一个新接口、一个定时任务、一个历史 SQL 脚本,漏掉一行 WHERE deleted = 0,问题就藏进去了。


















