软删除字段应选is_deleted(BOOLEAN/TINYINT)或deleted_at(DATETIME,默认NULL),避免status等模糊命名;查询须自动过滤,推荐PostgreSQL行级安全或MySQL视图封装,而非依赖人工WHERE。

软删除字段命名和类型怎么选才不容易出错
软删除本质是用 UPDATE 替代 DELETE,靠一个标记字段区分“逻辑删除”和“真实存在”。最稳妥的做法是用 is_deleted(布尔型)或 deleted_at(时间戳型),别用 status、flag 这类模糊字段——后续加其他状态时会冲突。
布尔型更轻量,但无法知道删于何时;时间戳型可查删除时间、支持按时间恢复,但需要数据库支持 TIMESTAMP 或 DATETIME 类型,默认值建议设为 NULL,非删除行保持为空,避免默认填 0000-00-00 引发兼容问题。
-
is_deleted推荐用BOOLEAN(PostgreSQL)或TINYINT(1)(MySQL),查询时写WHERE is_deleted = FALSE,别依赖!= 1这类隐式转换 -
deleted_at建议加索引,尤其当表数据量大且常查“未删除数据”时,否则WHERE deleted_at IS NULL可能全表扫描 - 千万别用
status TINYINT表示“0=正常, 1=删除, 2=审核中”,这会让 WHERE 条件越来越难维护
UPDATE 语句里怎么安全加条件防止误删
软删除不是无脑改字段,必须带业务约束。比如只允许删除自己创建的订单,或只允许删除“待支付”状态的订单——这些限制得写死在 UPDATE 的 WHERE 子句里,不能靠应用层校验兜底。
典型错误是写成:UPDATE orders SET deleted_at = NOW() WHERE id = ?,漏掉状态/权限判断,结果管理员点错 ID 就把已发货订单删了。
- 必须同时校验业务状态:
WHERE id = ? AND status = 'pending' AND user_id = ? - 涉及多表关联校验时(如只允许删除本人名下且未被引用的地址),优先在应用层先查再删,而不是硬塞进单条 UPDATE 的 JOIN —— MySQL 对 UPDATE + JOIN 的 WHERE 权限控制不直观,容易绕过
- 加
AND deleted_at IS NULL是保险做法,避免重复执行触发多次更新(虽然不影响结果,但可能干扰审计日志)
如何让 SELECT 自动过滤已删除数据不靠人肉加 WHERE
靠开发每次写 SQL 都记得加 WHERE deleted_at IS NULL 不现实,容易漏。真正可持续的做法是用视图或数据库行级安全(RLS),而不是指望团队自觉。
PostgreSQL 可直接开 RLS:CREATE POLICY not_deleted ON users FOR SELECT USING (deleted_at IS NULL);MySQL 没原生 RLS,就得靠视图:
CREATE VIEW active_users AS SELECT id, name, email FROM users WHERE deleted_at IS NULL;
然后让所有业务查询走 active_users 视图,而不是原表。注意:视图不能替代索引,deleted_at 字段仍需单独建索引,否则视图查询照样慢。
- 别用存储过程封装“安全 SELECT”,调用链一深就失控,ORM 也难适配
- 如果用 ORM(如 Django、Laravel),优先配置全局 soft-delete scope,比手写 SQL 更可靠
- 测试时务必构造已删除数据,验证它们真的不会出现在任何列表接口里——这是最容易被忽略的漏测点
恢复软删除数据时要注意什么
恢复操作不是简单 SET NULL,得检查约束是否还成立。比如恢复一个订单前,要确认它关联的用户是否还存在、库存是否仍充足,否则直接恢复会导致数据不一致。
最简方案是反向 UPDATE:UPDATE orders SET deleted_at = NULL WHERE id = ? AND deleted_at IS NOT NULL,但前提是业务上允许“无条件恢复”。
- 如果恢复需审批或有时间窗口(如仅允许删除后 7 天内恢复),WHERE 条件里必须加上:
AND deleted_at >= NOW() - INTERVAL '7 days' - 恢复后建议触发一次状态同步(比如重新计算订单总金额、更新用户统计),别假设数据仍是“干净”的
- MySQL 中对
deleted_at字段设为NULL后,若该列有NOT NULL约束,会报错——建表时就得定好允许 NULL
软删除看着只是改个字段,但真正落地时,90% 的坑都出在边界条件没想全:比如并发执行两次删除、恢复时外键约束失效、归档任务误扫 deleted_at 非空数据。与其事后补救,不如从字段设计、UPDATE 条件、查询入口三处卡死。

















