软删除本质是标记而非删除,需模型启用softdeletes trait且数据库含deleted_at字段;restore()仅对软删记录有效,硬删数据不可恢复;查软删数据须用withtrashed()或onlytrashed()。

软删除不是“删了还能找回来”,而是根本没删——只是加了个 deleted_at 时间戳标记。恢复操作只对这类记录有效,硬删(forceDelete() 或原生 DELETE FROM)的数据无法用 restore() 拿回。
模型必须正确启用 SoftDeletes
缺这一步,所有后续操作都无效。不是加个 trait 就完事,还要确认三件事:
- 模型类中已
use Illuminate\Database\Eloquent\SoftDeletes;并在 class 定义里声明use SoftDeletes; - 数据库表里存在
deleted_at字段,类型为TIMESTAMP NULL(不能是DATETIME,Laravel 8+ 对类型更敏感) -
$casts或$dates中未意外覆盖deleted_at的类型解析(比如写成'deleted_at' => 'date'会破坏 restore 逻辑)
查不到软删记录?检查 withTrashed() 和 onlyTrashed()
Post::find(1) 默认忽略软删数据,这是最常踩的坑。要操作软删记录,必须显式带上作用域:
-
Post::onlyTrashed()->find(1):只查已软删的那条 -
Post::withTrashed()->find(1):不管删没删,都查出来(deleted_at为NULL表示未删) -
Post::withTrashed()->where('id', 1)->first()等价于上一条,但更易读
如果 onlyTrashed()->find(1) 返回 null,不是代码错,而是这条记录压根没被软删过——可能已被硬删,或 delete() 根本没执行成功。
restore() 失败的典型原因
$post->restore() 看似简单,但失败时往往静默无报错。重点排查:
- 模型事件监听器里有没有在
restoring或restored钩子中抛出异常(比如校验失败后return false) - 数据库字段权限问题:
deleted_at被设为NOT NULL DEFAULT CURRENT_TIMESTAMP,导致 update 时无法设为NULL - 事务中调用
restore()后未提交,或被外层事务 rollback —— 此时查库会发现deleted_at还在 - 使用了自定义主键(如
protected $primaryKey = 'uuid';),但查询时仍用数字 ID,导致find(1)找不到
批量恢复与关联数据注意点
单条恢复用 restore() 没问题,但批量或带关联时容易漏细节:
-
Post::onlyTrashed()->where('user_id', 123)->restore()是合法的,但不会触发模型的restored事件(只有实例化后的对象调用才触发) - 如果 Post 关联了 Comment,且 Comment 也启用了软删除,
restore()不会级联恢复评论——得手动查Comment::onlyTrashed()->where('post_id', $post->id)->restore() - 恢复后记得清空相关缓存(比如 Redis 里存的
post:1),否则前端可能还在读旧快照
软删除真正的复杂点不在语法,而在于业务语义:什么时候该软删、哪些字段要随恢复重置(比如 updated_at 是否应更新)、软删状态是否要同步到搜索索引或消息队列——这些都不会自动发生,全靠你在钩子里补全。











