Laravel软删除默认排除已删数据,withTrashed()可临时包含,onlyTrashed()仅查已删数据;关系查询需显式调用withTrashed(),全局强制开启会破坏软删除语义。

软删除模型默认不查已删数据,withTrashed() 是开关
Laravel 的软删除不是“删了就没了”,而是给记录打个 deleted_at 时间戳标记。所以默认所有查询(get()、find()、关系查询)都会自动加 WHERE deleted_at IS NULL 条件——你根本看不到已软删的行。
想临时看到它们?必须显式调用 withTrashed():
App\Models\Post::withTrashed()->where('id', 123)->first();
这个方法会移除 deleted_at IS NULL 限制,但保留其他 where 条件。注意它只对当前链式调用生效,不影响后续静态调用。
onlyTrashed() 只查已删数据,别和 withTrashed() 搞混
如果你要专门处理回收站场景(比如列出所有已删文章),onlyTrashed() 才是正解。它会把查询条件反向变成 WHERE deleted_at IS NOT NULL:
App\Models\Post::onlyTrashed()->get(); // 返回所有软删的 Post
-
withTrashed()= “正常查 + 包含已删” -
onlyTrashed()= “只查已删,完全不看未删的” - 两个方法互斥,不能连用:
withTrashed()->onlyTrashed()会报错或行为不可靠 - 它们都不影响模型的
bootSoftDeletes()行为,只是查询层面的修饰器
关系查询里漏掉 withTrashed() 就查不到软删关联项
软删除的影响会穿透到 Eloquent 关系。比如一个 User 有多个 Post,而某个 Post 被软删了:
$user->posts; // 默认查不到那个软删的 Post
除非你在定义关系时就声明允许包含已删数据:
public function posts()
{
return $this->hasMany(Post::class)->withTrashed();
}
或者临时在调用时加:
$user->posts()->withTrashed()->get();
- 关系方法上没写
withTrashed(),就算父模型本身用了,子模型依然被过滤 -
withTrashed()必须加在关系构造器上(即posts()返回的 Builder 实例),不能加在模型实例上 - 如果用了 Eager Loading(
with('posts')),同样需要在关系定义里提前声明,否则预加载时照样丢掉软删项
全局作用域下硬编码 withTrashed() 很危险
有人为了“统一查全部”,在模型的 boot() 里偷偷加全局作用域强制包含软删数据:
protected static function booted()
{
static::addGlobalScope('force-with-trashed', function (Builder $builder) {
$builder->withTrashed();
});
}
这会导致所有查询(包括后台管理、统计、API 接口)全都绕过软删除逻辑,极易引发数据误展示或权限越界。
- 软删除的核心价值是逻辑隔离,不是存储开关;强行全局放开等于废掉这个机制
- 真正需要“始终可见”的字段(如日志、审计记录),应该单独建表,不走软删除模型
- 如果某些控制器/服务类确实高频用到已删数据,建议封装成专用 Repository 方法,而不是污染模型全局行为
软删除不是开关,是语义层约束。开哪条缝、开多久、谁有权开——得靠调用方自己拿捏,模型只负责守好那道 deleted_at 的门。


















