thinkphp软删除需显式调用withtrashed()或onlytrashed()才能查已删记录,且必须置于查询链首位;restore()失败多因字段未配置或类型不匹配;性能问题可通过delete_time索引优化。

软删除字段没生效,查不到已删记录
ThinkPHP 的 softDelete 默认只拦截「查询未删除数据」,不会自动把已删记录加进结果里。想查包含已删的,得手动绕过软删除逻辑。
常见错误是直接用 select() 或 find(),结果空荡荡——因为框架默认加了 delete_time = null 条件。
- 用
withTrashed()显式包含已删数据:UserModel::withTrashed()->select() - 只查已删的,用
onlyTrashed():UserModel::onlyTrashed()->select() - 注意:这两个方法必须在查询构建器链最前面调用,放错位置(比如 after
where())会失效
restore() 失败但没报错
restore() 看似执行成功,实际数据库里 delete_time 没清空,大概率是模型没配对软删除字段或时间类型不匹配。
ThinkPHP 要求软删除字段必须是可写入的数据库字段,默认是 delete_time,类型推荐 datetime 或 int(时间戳),不能是 varchar 或表达式字段。
- 确认模型里定义了
protected $deleteTime = 'delete_time'; - 如果用时间戳,加
protected $type = ['delete_time' => 'integer'];防止自动转字符串 -
restore()对象必须是从onlyTrashed()查出来的,普通find()结果调用会静默忽略
where 条件和 withTrashed() 顺序搞反了
很多人写成 UserModel::where('status', 1)->withTrashed()->select(),结果还是没包含已删数据——因为 where 提前触发了查询构造,withTrashed() 后置无效。
ThinkPHP 的查询构建器是链式调用,但软删除相关方法必须是「第一个」条件动作,否则底层 SQL 已经固化。
- 正确顺序:
UserModel::withTrashed()->where('status', 1)->select() - 带 join 时更危险:
join()后再调withTrashed()通常不生效,得把软删除逻辑提到整个链最前端 - 不确定时,用
buildSql()看生成的 SQL,确认delete_time条件是否被移除
软删除 + 时间范围查询性能掉得厉害
加了 withTrashed() 后,原本走 delete_time 索引的查询可能变全表扫描,尤其当已删数据占比高时。
根本原因是 ThinkPHP 为兼容各种数据库,生成的 SQL 把 delete_time IS NULL OR delete_time > 0 这类条件塞进 WHERE,导致索引失效(特别是 MySQL 对 OR 优化差)。
- MySQL 下建议给
delete_time字段单独建索引:ALTER TABLE user ADD INDEX idx_delete_time (delete_time); - 如果业务上「查已删数据」是低频操作,别长期开着
withTrashed(),按需开启 - 高频场景可考虑冗余一个
is_deletedtinyint 字段,配合普通索引,用where('is_deleted', 0)替代软删除逻辑
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










