软删除未生效的根本原因是模型未正确启用软删除功能,需同时满足:继承eloquent\model、声明use softdeletes、数据表含timestamp/datetime类型的deleted_at字段且迁移中调用softdeletes()。

软删除字段没生效,deleted_at 始终为 null
根本原因通常是模型没正确启用软删除——Laravel 不会自动开启,必须显式声明。光加 use SoftDeletes 不够,还得确认模型类真正继承了 Illuminate\Database\Eloquent\Model(而不是手写基类漏掉 trait),且数据库表里真有 deleted_at 字段(类型必须是 TIMESTAMP NULL 或 DATETIME NULL)。
实操建议:
- 检查迁移文件是否包含
$table->softDeletes()(它等价于$table->timestamp('deleted_at')->nullable()) - 确认模型顶部写了
use Illuminate\Database\Eloquent\SoftDeletes;和use SoftDeletes; - 运行
php artisan migrate:fresh --seed重置环境时,别跳过迁移——手动删表再建容易漏掉deleted_at - 用
dd(User::withTrashed()->first())看字段是否存在,排除字段名被自定义(如改成了is_deleted)的情况
restore() 找不到数据,明明记得刚 delete() 过
软删除后数据仍在表里,但默认查询(包括 find()、where()、Eloquent 关联)全部自动加了 WHERE deleted_at IS NULL 条件。所以 restore() 前必须先「查到」这条软删除记录,否则调用会静默失败(返回 false)。
实操建议:
- 恢复前务必用
withTrashed()或onlyTrashed()查找,例如:User::withTrashed()->find(123) - 别对已软删除的模型实例直接调
$user->restore()——如果该实例是通过普通查询得到的,它根本没加载deleted_at值,restore()会因找不到软删除状态而跳过 - 批量恢复要小心:
User::onlyTrashed()->where('created_at', 'subWeek())->restore()是安全的;但User::where(...)->restore()永远不会命中软删除行
关联数据没跟着软删除,或恢复时子记录丢失
Laravel 的 SoftDeletes 是单模型行为,不递归处理关联。比如 Post 软删除,它的 comments 依然存在且可查——除非你手动在 Comment 模型也启用软删除,并在 Post 删除时主动触发。
实操建议:
- 需要级联软删除?在父模型监听
deleting事件,手动调$this->comments()->update(['deleted_at' => now()])(注意:不能用delete(),否则会触发子模型自己的 deleting 事件,可能死循环) - 恢复时同理:监听
restoring,再手动恢复子记录(Comment::withTrashed()->where('post_id', $this->id)->restore()) - 外键约束无影响——软删除不走
DELETE FROM,所以ON DELETE CASCADE完全不触发
MySQL 8.0+ 用 json 字段存软删除标识?别这么干
有人想绕过 deleted_at 字段,改用 status JSON 字段标记 {"deleted": true},再写全局 scope。这会导致所有 Eloquent 软删除方法(withTrashed、restore、trashed())完全失效,且无法利用数据库索引加速查询(JSON 字段上的 WHERE JSON_CONTAINS(status, '"deleted": true') 几乎没法优化)。
实操建议:
- 坚持用原生
deleted_at字段——它是 Laravel 软删除的契约,也是性能和兼容性的底线 - 如果业务真需要多状态(如 draft/published/deleted),用独立状态字段 +
deleted_at共存,不要混进 JSON - MySQL 5.7+ 支持生成列,可建虚拟列
is_deleted AS (deleted_at IS NOT NULL)加索引,但没必要——直接给deleted_at加索引更直白
软删除真正的复杂点不在代码怎么写,而在于「什么时候不该用」:高频更新的统计类表、硬删除合规要求场景、或关联逻辑极其复杂的聚合根,这时候强行套软删除反而让代码和 SQL 更难理清。留着 deleted_at 字段,不等于每条数据都得走它。











