laravel软删除后查不到记录是因为eloquent自动添加where deleted_at is null条件,需用withtrashed()或onlytrashed()显式查询软删数据,且deleted_at字段须提前在迁移中定义并建议加索引。

软删除后查询为什么查不到记录
因为 Laravel 默认在所有 Eloquent 查询中自动添加 where deleted_at is null 条件,这是通过全局作用域(SoftDeletingScope)实现的。只要模型用了 SoftDeletes trait,这个过滤就无处不在。
常见错误现象:Model::find(1) 返回 null,但数据库里 deleted_at 不为空——不是查不到,是被自动过滤掉了。
- 使用场景:日常列表、详情、关联查询全都会触发该过滤
- 不影响
DB::table()原生查询,只影响 Eloquent 模型操作 - 如果你用
toSql()查看生成的 SQL,会发现多出and `deleted_at` is null
想查包括已软删的记录怎么办
必须显式移除全局作用域,不能靠改 where 条件绕过——因为作用域在构建阶段就加进去了,whereNotNull('deleted_at') 会被它覆盖或冲突。
正确做法只有两个:
-
Model::withTrashed()->get():查全部(含deleted_at IS NOT NULL的) -
Model::onlyTrashed()->get():只查已软删的(deleted_at IS NOT NULL)
注意:withTrashed() 对 find()、first()、where() 等所有链式方法都生效;但它不会影响关联查询的子句,除非你在关联定义里也加了对应作用域。
restore() 和 forceDelete() 容易踩的坑
restore() 不是简单清空 deleted_at 字段,它会触发 restoring / restored 事件,并重新走模型的保存逻辑(比如自动更新 updated_at)。而 forceDelete() 是彻底删库,跳过软删除流程,也不触发 deleting / deleted 事件。
- 如果模型有
boot()里写的static::deleting()回调,forceDelete()不会执行它 -
restore()后,关联关系不会自动恢复——比如软删订单时顺带软删了订单项,恢复订单不会连带恢复订单项 - 批量操作要小心:
Model::withTrashed()->where(...)->restore()是合法的,但forceDelete()不能直接链式调用,得用each(fn($m) => $m->forceDelete())
软删除字段名和时间类型能自定义吗
可以,但改完就得自己担着兼容性问题。默认字段是 deleted_at,类型是 timestamp nullable。Laravel 强依赖这个约定来拼 SQL 和判断状态。
自定义方式只有两个地方:
- 在模型里重写
const DELETED_AT = 'is_deleted';(仅支持字符串字段名,不支持时间戳以外的类型) - 若想用整数标记(如
status = 2表示已删),就不能用SoftDeletes,得手写作用域 + 手动覆盖boot()中的删除逻辑
性能影响很小,但要注意:MySQL 对 IS NULL 字段的索引效率偏低,如果软删数据占比高,建议给 deleted_at 单独建索引($table->index('deleted_at');)。
真正容易被忽略的是迁移顺序:软删除字段必须在启用 SoftDeletes trait 之前就存在,否则首次运行 delete() 会报错 SQLSTATE[HY000]: General error: 1364 Field 'deleted_at' doesn't have a default value。











