软删除需同时满足模型引入SoftDeletes trait且数据表存在deleted_at字段,否则delete()为物理删除;withTrashed()等仅作用于当前查询链;restore()须在已识别软删状态的实例上调用。

软删除在 Laravel 中不会自动生效,必须同时满足两个硬性条件:模型引入 SoftDeletes trait,且数据库表存在 deleted_at 字段。缺一不可,否则 delete() 就是真删。
为什么 delete() 后数据彻底消失了?
这是最常遇到的“假失效”现象,本质是配置没对齐:
- 模型里漏写了
use SoftDeletes;—— 即使数据库有deleted_at字段,Laravel 也当它不存在 - 迁移加了字段,但没运行
php artisan migrate,或者迁移失败后没检查日志 - 字段名写成
is_deleted或deleted_time——SoftDeletestrait 硬编码只认deleted_at,改名等于没配 - 字段类型用了
tinyint或boolean——softDeletes()生成的是TIMESTAMP NULL,非时间类型会导致whereNull('deleted_at')行为异常
withTrashed() 和 onlyTrashed() 总是查不到数据?
这两个方法不是开关,而是查询修饰符,只影响当前链式调用:
-
User::withTrashed()->find(1)能查到已软删的用户;但紧接着写User::all(),还是看不到——因为没再加withTrashed() -
User::onlyTrashed()->get()返回空,先确认数据库里deleted_at确实是非 NULL 值(比如'2026-04-25 10:22:33'),而不是'0000-00-00 00:00:00'或空字符串 - 如果模型继承自基类(如
BaseModel),SoftDeletes必须显式写在具体模型里,父类声明无效 - 别在查询中混用
withoutGlobalScopes()—— 它会直接绕过软删除全局作用域,导致withTrashed()失效
restore() 没反应,deleted_at 还在?
restore() 不是万能的,它只对“已被识别为软删状态”的模型实例起作用:
- 错误做法:
User::find(1)->restore()——find()默认过滤掉软删记录,拿到的是 null 或未软删模型,调用restore()没意义 - 正确做法:
User::onlyTrashed()->find(1)->restore()或User::withTrashed()->findOrFail(1)->restore() - 批量恢复必须用查询构造器:
User::onlyTrashed()->where('team_id', 5)->restore(),别手写update(['deleted_at' => null]),后者跳过事件和验证 -
restore()不触发saving/updating事件,只发restoring和restored—— 如果你依赖updated_at自动更新或缓存同步,得手动补逻辑
关联模型不跟着软删除?
Laravel 默认不做级联软删除,父子关系完全独立:
- 用户软删,其文章仍可见(除非
Post模型自己也启用了SoftDeletes) -
$user->posts默认不包含已软删的Post,要查得显式写:$user->posts()->withTrashed()->get() - 想实现“删用户 → 所有文章也软删”,得在模型事件里手动处理:
static::deleting(function ($user) { $user->posts()->update(['deleted_at' => now()]); }); - 外键约束(如
onDelete('cascade'))对软删除完全无效,它只响应物理DELETE语句
最容易被忽略的是:软删除后,deleted_at 字段本身可读可写,但直接赋值 $model->deleted_at = null 不会触发任何钩子,也不会改变模型的 trashed() 状态判断;必须走 restore() 或 delete() 才能同步内部状态和事件流。


















