thinkphp软删除没生效的主因是模型未正确引入softdelete trait、deletetime字段名或类型不匹配、delete()调用方式错误;必须use think\model\concern\softdelete,显式声明$deletetime且数据库字段为null默认值,仅实例调用delete()或usesoftdelete(true)才触发软删除。

软删除没生效,90% 是模型没配对、字段类型不对、或 delete() 调用方式错了——不是框架bug,是配置链断了。
模型里没正确 use SoftDelete trait
ThinkPHP 不会自动启用软删除,哪怕你数据库加了 deleted_at 字段,模型里没声明,delete() 就是物理删。
- 必须在模型类顶部写
use think\model\concern\SoftDelete;(注意不是traits\model\SoftDelete,那是 TP5 旧路径,TP6/TP7 已移至model\concern) - 紧接着加
use SoftDelete;(这是 trait 引入语句,不能漏) - 如果用了 IDE 或自动导入,小心它帮你补全成带反斜杠的完整命名空间,比如
use \think\model\concern\SoftDelete;—— 这会导致 trait 没被真正混入
软删除字段名或类型不匹配
字段名不一致、默认值非 NULL、类型不支持时间戳,都会让软删除“看起来没反应”。
-
$deleteTime必须显式声明,且值要和数据库字段名完全一致,比如:protected $deleteTime = 'deletetime';(FastAdmin 常用)或'deleted_at' - 字段类型建议用
DATETIME NULL或INT NULL,**绝对不能设默认值为0或''** —— TP 判断逻辑是「是否为 NULL」,不是「是否为 0」 - 如果用整数标记(如
status = -1),就别硬套SoftDelete,它只认时间戳或 NULL 值逻辑 - TP6+ 支持数组写法:
protected $deleteTime = ['deletetime', 'datetime'];,但改了就要同步确认auto_timestamp配置是否开启
delete() 方法调用姿势错误
静态方法调用(如 User::destroy() 或 User::where()->delete())默认不走软删除,除非显式传参或用实例方法。
-
User::destroy($id)→ 物理删除(TP5/TP6 默认行为) -
User::destroy($id, true)→ TP5 可触发软删除,但 TP6+ 已废弃该参数,不再可靠 - 安全写法只有两种:
– 实例方式:$user = User::find($id); $user->delete();
– 静态方式:User::where('id', $id)->useSoftDelete(true)->delete();(TP6.3+ 新增,需确认版本) - 别写
User::where()->delete(),它绕过所有软删除逻辑,直连 SQL DELETE
withTrashed() 和 onlyTrashed() 被误用在写操作上
这两个方法只影响「读」,对 update()、save()、delete() 完全无效——写了也白写,还容易产生错觉。
-
User::withTrashed()->where('id', 1)->update(['name' => 'x'])→ 依然会更新已软删除的记录 - 想安全更新?必须手动加条件:
->where('deletetime', null)(字段名按你配置的来) - 想更新已删除数据?得明确意图:
User::onlyTrashed()->where('id', 1)->update(...),或者用withoutGlobalScope(SoftDelete::class) - 关联查询中,
with(['relation'])不会自动给关联表加软删除过滤,每个关联模型都得自己声明use SoftDelete并在关联定义里处理
最容易被忽略的是字段默认值和 destroy() 的静默行为:数据库字段设了 DEFAULT 0,一插入就等于“已被删除”;而 destroy() 成功返回 1 却没进回收站,问题就藏在这两处。动手前先跑一遍 User::withTrashed()->select(),看字段值是不是真变 NULL 了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











