逻辑删除用deleted_at而非delete,因其通过可空datetime字段标记删除状态,实现数据可恢复、审计合规、关联不崩;laravel等orm自动处理过滤与恢复,原生sql需严格使用is null条件并注意join和分页一致性。

为什么用 deleted_at 而不是直接 DELETE
逻辑删除本质是“标记删除”而非物理移除,核心就靠一个可为空的 deleted_at 字段(通常是 DATETIME 或 TIMESTAMP 类型)。它让数据可恢复、审计有据、关联不崩——比如订单已发货,但用户误点删除,物理删了就真没了;而用 deleted_at,只要没清空该字段,查的时候加个条件就能“复活”。
注意:MySQL 中别用 NOT NULL DEFAULT '0000-00-00 00:00:00',这在严格模式下会报错;应设为 NULL 默认值。
Laravel Eloquent 自动处理 deleted_at
Eloquent 原生支持软删除,只需两步:
- 模型中 use
Illuminate\Database\Eloquent\SoftDeletestrait - 数据库迁移里添加
$table->softDeletes()(等价于$table->timestamp('deleted_at')->nullable())
之后所有 delete() 调用都会自动写入当前时间到 deleted_at,且默认查询会自动排除 deleted_at IS NOT NULL 的记录。
要查含已删除的数据?用 withTrashed();只查已删除的?用 onlyTrashed()。别手写 whereNotNull('deleted_at'),Eloquent 的封装已经覆盖了索引友好性和 NULL 安全性。
原生 SQL 或非 Laravel 场景下怎么写安全查询
手动拼 SQL 时,deleted_at IS NULL 是基本过滤条件,但容易漏或写反。常见错误:
- 写成
deleted_at = NULL→ 永远不成立,要用IS NULL - JOIN 多表时,只在主表加条件,从表没过滤 → 查出“半删除”脏数据
- 分页 + 排序时没把
deleted_at加进ORDER BY,导致同一页数据前后不一致
示例(安全查询用户及其未删除的订单):
SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.deleted_at IS NULL WHERE u.deleted_at IS NULL;
注意:JOIN 条件里也得带上 o.deleted_at IS NULL,否则会拉出已删除订单的空行干扰业务逻辑。
restore() 和强制物理删除的边界在哪
restore() 只是把 deleted_at 设为 NULL,但它不会自动修复外键约束或触发器里的状态。比如订单恢复后,关联的支付记录若已被归档,就得手动同步。
真要物理删?必须显式调用 forceDelete()(Eloquent)或手写 DELETE FROM ... WHERE deleted_at IS NOT NULL。但上线前务必确认:
- 是否有定时任务定期清理超期
deleted_at数据(比如保留 90 天) - 备份策略是否包含软删除数据(有些 DBA 会跳过
deleted_at IS NOT NULL的行) - 全文搜索、ES 同步等下游系统是否监听了软删除事件并做了对应处理
最常被忽略的一点:软删除字段本身不是事务安全的“开关”,如果业务里混用原生查询和 ORM,很容易一方删了另一方还读得到——统一入口、统一抽象层,比字段本身重要得多。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











