Laravel软删除需手动配置:模型引入SoftDeletes trait并声明$dates,迁移中用softDeletes()添加deleted_at字段;默认查询自动过滤已软删数据,可用withTrashed()或onlyTrashed()查询,restore()恢复记录,forceDelete()彻底删除。

软删除不是自动开启的,必须手动配置模型和迁移,否则删了就真没了。
怎么让模型支持软删除
Laravel 的软删除依赖 Illuminate\Database\Eloquent\SoftDeletes trait,它会给模型增加一个 deleted_at 字段判断逻辑,并覆盖默认的 delete() 行为。
- 在模型类里 必须 引入 trait:
use SoftDeletes; - 模型需声明
$dates = ['deleted_at'];(Laravel 9+ 可省略,但显式写上更稳妥) - 数据库迁移中 必须 添加
deleted_at字段,用$table->softDeletes();(它等价于$table->timestamp('deleted_at')->nullable();) - 没加 trait 时调用
delete()是硬删;加了但没迁字段,会报错SQLSTATE[42S22]: Column not found: 1054 Unknown column 'deleted_at' in 'where clause'
软删除后数据还查得到吗
默认情况下,Eloquent 会在所有查询里自动加上 WHERE deleted_at IS NULL 条件,所以软删后的记录对普通 get()、find()、where() 都不可见。
- 要查包括已软删的数据:用
withTrashed(),比如User::withTrashed()->find(1) - 只查已软删的数据:用
onlyTrashed(),比如User::onlyTrashed()->get() -
restore()可恢复单条记录,restore()不会触发creating或updating事件,但会触发restoring和restored - 注意:
forceDelete()才是彻底删除,绕过软删逻辑
关联模型的软删除行为不联动
父模型软删除,子模型不会自动软删,也不会自动被排除查询 —— Laravel 默认不处理这种级联。
- 想实现“用户软删,其文章也软删”,得手动在模型事件里写:
static::deleting(function ($user) { $user->posts()->update(['deleted_at' => now()]); }); - 或者用第三方包如
laravel-soft-deleting-relations,但要注意它可能干扰原生的withTrashed()行为 - 外键约束(如
foreignId('user_id')->constrained()->onDelete('cascade'))对软删除无效,onDelete('cascade')只响应硬删 - 如果子表没加
SoftDeletestrait,restore()父模型时,子数据仍不可见,因为它们根本没进回收站
软删除字段名可以改,但别轻易动
默认字段叫 deleted_at,可以通过重写模型的 $dates 和 $softDelete 属性来换,但代价不小。
- 改字段名需同步改迁移、模型、所有手动写的 SQL 查询(比如
DB::table()->whereNotNull('my_deleted_flag')) - 第三方包(如 Scout、Nova)可能硬编码依赖
deleted_at,改了会出兼容问题 - Laravel 自带的
SoftDeletestrait 里有 3 处硬引用deleted_at,自定义字段需重写整个 trait 逻辑,不推荐 - 真有合规要求必须用别的字段名?优先考虑用视图或中间表映射,而不是动核心字段
软删除本质是“逻辑标记 + 查询过滤”,它不改变数据物理存在,也不解决并发误删、权限越界或审计追溯问题。真正关键的是:字段有没有加索引、deleted_at 是否参与联合查询条件、以及 restore 前要不要校验业务状态 —— 这些地方一漏,就容易出现数据可见性错乱。


















