软删除未生效的根本原因是模型未同时满足三个条件:引入softdeletes trait、正确声明$dates或$casts、数据库deleted_at字段为nullable timestamp/datetime;查询默认过滤软删数据,需withtrashed()等方法显式包含;restore()是实例方法且仅对软删记录有效;关联数据需手动处理。

软删除字段没生效,deleted_at 始终为 null
根本原因通常是模型没真正启用软删除——不是加了 use SoftDeletes 就完事。Laravel 要求模型同时满足三个条件:引入 trait、声明 $dates(或 Laravel 9+ 的 $casts)、数据库字段存在且类型正确。
-
deleted_at字段必须是nullable timestamp(MySQL)或nullable datetime,不能是string或integer - Laravel 8 及以前需在模型中显式添加:
protected $dates = ['deleted_at'];;Laravel 9+ 推荐用protected $casts = ['deleted_at' => 'datetime']; - 如果用了自定义字段名(比如
is_deleted),SoftDeletestrait 不会识别——它只认deleted_at
查询时查不到已软删除的数据,但业务需要“看到”它们
默认所有 Eloquent 查询都会自动加上 WHERE deleted_at IS NULL,这是软删除的核心机制,但也是最容易困惑的点:你以为数据“还在”,其实它已经被过滤掉了。
- 要查包括已软删的记录,用
withTrashed():User::withTrashed()->find(123) - 只查已被软删的,用
onlyTrashed():User::onlyTrashed()->where('email', 'test@example.com')->get() - 注意:关联查询不会自动继承软删除状态,
user->posts默认不包含软删的 post,得手动在关系定义里加->withTrashed()
调用 restore() 没反应,或者报错 “Call to undefined method”
restore() 是实例方法,不是静态方法。常见错误是直接在查询构造器上调用,比如 User::where('id', 1)->restore() —— 这会失败,因为 restore() 不在 Builder 类里。
- 正确做法是先获取模型实例:
User::withTrashed()->find(1)->restore() - 批量恢复必须用集合操作:
User::onlyTrashed()->get()->each->restore(),不能用update()直接设deleted_at = null,否则事件(如restoring/restored)不会触发 - 如果模型被硬删除过(即执行过
forceDelete()),restore()无效——软删除只对曾经软删、未被强制清除的记录有效
软删除后关联数据没处理,外键约束或业务逻辑出问题
软删除只作用于当前模型,Eloquent 不会自动软删关联数据,也不会检查外键引用。这常导致“用户已删除,但他的订单还显示着”,或更糟:数据库级外键冲突(如果设了 ON DELETE CASCADE 但又想软删)。
- 避免在数据库设
CASCADE删除,软删除和物理级级联天然冲突 - 需要联动软删时,手动处理:在模型的
deleting事件里调用关联模型的delete(),例如:$this->posts()->delete() - 查询带关联时,记得给关联也加软删除支持,比如在
Post模型里同样启用SoftDeletes,否则$user->posts仍可能返回已软删的 post(取决于关系定义是否加了withTrashed())
软删除看着简单,真正落地时最麻烦的是边界:什么时候该查带删的、什么时候不该;事件触发时机是否符合预期;关联模型是否同步处理。这些地方一漏,数据状态就容易“半死不活”。









