thinkphp 6软删除需模型引入softdelete trait、显式声明$deletetime、数据库字段为nullable datetime/integer,且delete()仅对模型方法生效;db类操作绕过软删,查询默认过滤已删数据,需withtrashed()或onlytrashed()显式控制。

软删除不是“开关式”功能,它依赖模型配置、字段定义、调用方式三者严格对齐——任一环节错配,delete() 就会静默变成真删,或查询完全过滤掉本该出现的数据。
模型里必须正确引入并声明 SoftDelete
ThinkPHP 6 不自动加载 SoftDelete,必须手动 use trait 并在类中启用:
-
use think\model\concern\SoftDelete;(TP6 路径,不是 TP5 的traits\model\SoftDelete) - 类定义中写
use SoftDelete; - 显式声明
protected $deleteTime = 'delete_time';,即使字段名是默认值也建议写上,避免继承时被覆盖 - 如果字段名不是
delete_time(比如叫is_deleted或deleted_at),必须同步改这里,否则delete()会更新错字段
数据库字段类型和约束必须匹配模型配置
字段类型不匹配是最隐蔽的失效原因——没报错,但软删不生效:
-
delete_time字段类型推荐DATETIME NULL,**不能设NOT NULL DEFAULT CURRENT_TIMESTAMP**,否则新插入记录就带时间,一入库就被判为“已删除” - 若用整数时间戳(
BIGINT),模型中必须加protected $type = ['delete_time' => 'integer'];,否则写入时被转成字符串再强转为 int,结果常为0 - 若用布尔型(如
is_deleted TINYINT(1)),SoftDelete特性不适用——它只认时间型字段,此时得手写 scope 或重写delete()
查询时软删逻辑默认生效,但绕过方式有明确规则
启用 SoftDelete 后,所有 select()、find()、where()->get() 都自动加 AND delete_time IS NULL 条件:
- 查全部(含已删):必须用
UserModel::withTrashed()->select(),且withTrashed()要放在链最前面 - 只查已删:用
UserModel::onlyTrashed()->select() -
withTrashed()和onlyTrashed()不能连用,后者会被前者覆盖 - 关联查询(如
with('posts'))默认不继承主模型的软删状态,子模型要单独加withTrashed():with(['posts' => function ($q) { $q->withTrashed(); }])
delete() 方法是否真软删,取决于你调用的方式
直接走 Db 类或原生 SQL 会彻底绕过软删逻辑:
- ✅ 正确(触发软删):
UserModel::destroy(123)、$user->delete() - ❌ 错误(真删):
Db::name('user')->delete(123)、Db::name('user')->where('id', 123)->delete() - 物理删除要显式传
true:UserModel::destroy(123, true)或$user->force()->delete() -
restore()是实例方法,只对单条有效;批量恢复需用where('delete_time', 'neq', null)->update(['delete_time' => null])
最常被忽略的是字段类型与模型 $type 声明的同步,以及关联模型软删需独立控制——这两个点不出问题时一切正常,一出就是数据查不到或误删,且很难从日志里发现。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











