软删除本质是Eloquent对delete()的重写,执行UPDATE设置deleted_at而非DELETE;必须调用模型实例delete()方法,不可用DB::statement()等原生SQL,否则不设deleted_at、不触发事件、关联软删失效。

软删除不是执行SQL,而是Eloquent模型行为
直接用 DB::statement() 或 DB::select() 执行原生 SQL 无法触发 Laravel 的软删除逻辑——deleted_at 字段不会被自动设置,模型事件(如 deleting、deleted)也不会触发,关联软删除更会完全失效。
软删除本质是 Eloquent 对 delete() 方法的重写,它不执行 DELETE FROM,而是生成 UPDATE ... SET deleted_at = NOW()。绕过模型直接写 SQL,就等于放弃整个软删除机制。
正确做法:调用模型实例的 delete() 方法
只要模型启用了软删除(使用 SoftDeletes trait),且数据库表有 deleted_at 字段,调用实例方法即可:
$user = User::find(123); $user->delete(); // ✅ 自动更新 deleted_at,触发事件,处理关联(如 withTrashed() / onlyTrashed() 可控)
常见误操作包括:
- 用
User::where('id', 123)->delete()—— 这是「查询构造器删除」,跳过模型,不软删,除非你显式用withTrashed()配合update()模拟,但极不推荐 - 手动写
DB::table('users')->where('id', 123)->update(['deleted_at' => now()])—— 字段名硬编码、时区可能错、不触发事件、不校验模型策略/权限 - 忘记在迁移中添加
$table->softDeletes(),导致字段不存在,delete()静默失败或报错SQLSTATE[HY000]: General error: 1364 Field 'deleted_at' doesn't have a default value
批量软删除必须走模型查询,不能用 DB facade
想软删多条记录?依然要通过模型发起,而不是 DB::table():
User::whereIn('status', ['inactive', 'banned'])->delete(); // ✅ 走模型,每行都设 deleted_at
注意两点:
- 该调用底层仍是逐行 UPDATE(Laravel 9+ 默认行为),不是单条 SQL;若需真正批量 UPDATE(性能敏感场景),得手写
DB::table()->where(...)->update(['deleted_at' => now()]),但这就脱离软删除语义,后续restore()、withTrashed()全部失效 - 批量 delete 不触发单个模型的
deleting事件(只触发一次deleting: App\Models\User),但会触发deleted事件(同样一次);如需逐行逻辑,必须用集合遍历 + 实例delete()
硬删前务必确认是否真要绕过软删除
有时你会看到 forceDelete() 或 withTrashed()->where(...)->forceDelete()。它们确实执行物理删除(DELETE FROM),但代价是:
- 不可逆(除非备份)
-
restore()失效 - 审计日志断层(如果靠
deleted_at做行为追踪) - 外键约束可能报错(尤其当关联表也软删但未配置级联)
真正需要硬删的场景极少,比如 GDPR 强制彻底清除用户数据。日常管理后台的「删除」按钮,应始终走 delete()。
最常被忽略的是:软删除后,count() 默认不包含已软删记录,但 withTrashed()->count() 会;这个差异在分页、统计看板里容易引发数据对不上问题。


















