ThinkPHP 的 delete() 是模型实例方法,不支持链式调用;真删需用 Db::delete() 或先查 ID 再 destroy();destroy() 仅支持主键删除,按非主键字段删须先查后删。

ThinkPHP 的 delete() 方法默认不走软删除
只要模型没启用软删除(即没配置 soft_delete 或没继承 SoftDelete trait),调用 delete() 就是真删。但很多人误以为它像 destroy() 那样能直接传条件数组——其实不能。
常见错误现象:UserModel::where('status', 0)->delete() 看似合理,却报错 Call to undefined method delete(),因为 delete() 是模型实例方法,不是查询构造器的静态方法。
- 正确做法是先查出主键 ID,再用
destroy()批量删:UserModel::where('status', 0)->column('id')拿 ID 列表,再UserModel::destroy($ids) - 或者更直接:用
Db::name('user')->where('status', 0)->delete()—— 这是原生 SQL 删除,不经过模型事件和验证,但最快最彻底 - 如果必须走模型逻辑(比如要触发
deleting事件),就得用each()+ 实例delete(),但大数据量时容易内存溢出
用 destroy() 批量删时字段条件怎么写
destroy() 本身不接受 where 条件,只认主键值或主键数组。想按非主键字段删,得先查后删,两步不能省。
使用场景:清理过期日志、禁用用户、归档订单等需带业务字段过滤的批量操作。
- 推荐组合:
$ids = LogModel::where('created_time', 'limit(1000)->column('id'),再LogModel::destroy($ids)—— 加limit防止单次删太多锁表 - 注意
column()返回的是纯数值数组,别漏掉这个中间步骤;若模型主键不是id,得显式写成column('log_id') - 别用
all()->each(...)拿模型实例再删,对象实例化开销大,1000 条就可能占几 MB 内存
硬删除绕过模型事件和验证的代价
用 Db::name(...)->where(...)->delete() 是最快的硬删方式,但它跳过了模型层所有钩子:不触发 deleting/deleted 事件,不校验权限,也不走事务自动包装(除非你手动开启)。
性能影响:比模型删快 3–5 倍;兼容性影响:在多态关联或需要级联删的场景下,完全失效。
- 如果关联了附件、缓存或第三方服务,必须自己补删逻辑,比如删前查出
file_path字段,再用unlink() - 事务安全起见,建议包在
Db::transaction()里,尤其涉及多个表联动清理时 - 测试环境可以加个日志埋点:
Log::info('hard delete user by status=0, count=' . $count),方便后续审计
TP6.1+ 中 forceDelete() 并不等于“强制硬删”
名字有误导性。forceDelete() 只在启用了软删除的模型中生效,作用是跳过软删除状态检查,真正执行物理删除。对没开软删除的模型,它根本不存在。
容易踩的坑:看到文档里有 forceDelete() 就以为是通用硬删接口,结果调用时报 Method forceDelete does not exist。
- 确认是否用了
use SoftDelete;和protected $deleteTime = 'delete_time'; - 即使开了软删除,
forceDelete()仍是实例方法,不能直接Model::where(...)->forceDelete() - 真要批量强删软删除数据:先
select()出模型实例,再逐个调forceDelete(),或改用Db::name(...)->where(...)->delete()
最易被忽略的一点:硬删操作一旦执行,数据库 binlog 里只有 DELETE 语句,没有字段快照。线上执行前,务必确认 WHERE 条件已用 SELECT 验证过结果集,宁可多跑一遍 count() 也别靠感觉。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











