软删除需同时满足模型注册全局作用域和数据库存在匹配类型的 deleted_at 字段,否则导致查询漏数据、restore() 静默失败或 SQL 错误;User::find(1) 不走全局作用域故查不到软删记录,而 User::all() 默认走作用域(若已注册)。

软删除不是加个 SoftDeletes trait 就自动生效的功能,它依赖两个硬性条件同时满足:模型必须显式注册全局作用域,且数据库表中必须存在类型匹配的 deleted_at 字段;缺一就会导致查询漏数据、restore() 静默失败,或直接报 SQL 错误。
为什么 User::find(1) 查不到已软删的记录,但 User::all() 却能?
这不是“有的查得到、有的查不到”,而是 find() 根本不走全局作用域,而 all() 走。Laravel 从 9 开始就不再隐式注册软删除作用域,find()、findOrFail()、first() 这类主键直查方法跳过所有全局作用域(包括软删除),只做原始 SELECT。所以即使你加了 SoftDeletes trait,没手动注册作用域,User::all() 也不会过滤 —— 它返回全部;而 User::find(1) 返回 null 时,你根本分不清是“不存在”还是“已被软删”。
实操建议:
- 确认模型是否在
booted()中调用了static::addGlobalScope(new SoftDeletingScope()) - 测试时别只用
find(),改用User::where('id', 1)->first()才能验证作用域是否生效 - 数据库种子插入测试数据时,手动设
deleted_at值后,必须用User::withTrashed()->find(1)加载才能调trashed()
restore() 静默失败的三个高频原因
$user->restore() 看似简单,但失败时通常不抛异常,只是数据库里 deleted_at 没变 NULL。最常见原因不是逻辑写错,而是底层约束卡住。
实操建议:
-
deleted_at字段被误设为NOT NULL或带DEFAULT CURRENT_TIMESTAMP,导致 UPDATE 无法写入NULL - 模型中定义了
restoring事件回调,但里面写了return false或抛了未捕获异常,中断了后续流程 - 在事务中调用
restore()后没commit,或者外层事务回滚,查库自然看不到变化 - 批量恢复别用
each()+ 实例调用 ——User::onlyTrashed()->where(...)->restore()才触发单条事件,且性能更好
withTrashed() 和 onlyTrashed() 不是“开关”,而是查询上下文切换
这两个方法不是给模型加开关,而是临时修改当前查询链的构建方式:前者移除 WHERE deleted_at IS NULL 条件,后者替换成 WHERE deleted_at IS NOT NULL。它们对 get()、first()、count() 全部生效,但不会影响关联加载 —— User::withTrashed()->with('posts')->get() 里的 posts 仍按 Post 模型自己的作用域走。
实操建议:
- 关联关系要受控,得在关系定义里加条件:
return $this->hasMany(Post::class)->whereNull('deleted_at'); - 别在控制器里到处写
->whereNull('deleted_at'),重复又易漏,应在Post模型的booted()中注册全局作用域 -
withoutGlobalScopes()会清掉所有作用域(含软删除),仅限可信后台导出等场景,API 层禁用
deleted_at 字段没索引,查询慢到怀疑人生
软删除默认加的 WHERE deleted_at IS NULL 条件,若字段无索引,MySQL 会全表扫描。尤其当表数据量过万,列表页响应时间可能从 50ms 涨到 2s+。
实操建议:
- 建复合索引:
$table->index(['deleted_at', 'id']);,覆盖常用主键查询 - 执行
DB::select('EXPLAIN SELECT * FROM users WHERE deleted_at IS NULL'),确认type是ref或range,不是ALL - 归档类表(如
logs)如果确定永不恢复,直接硬删除 + 移除SoftDeletes,比维护索引更省资源
真正容易被忽略的是:软删除字段类型和作用域注册是强耦合的,改字段名不光要重写 DELETED_AT 常量,还得同步加进 $dates 数组,否则 restore() 会因类型转换失败而静默跳过。











