ThinkPHP中count()统计软删除数据返回0,因count()绕过withTrashed()的where配置而走硬查询;应改用where('delete_time', '', null)->count()或Db::raw('COUNT(*)')配合withTrashed()。

ThinkPHP 查询软删除数据时 count() 返回 0 怎么办
直接原因:ThinkPHP 的 count() 默认走的是「硬查询」,自动过滤掉 delete_time IS NOT NULL 的记录。哪怕你已经调用过 withTrashed(),count() 仍不生效——这是底层 Query 类对聚合函数的特殊处理逻辑。
实操建议:
- 改用
all()+count()(不推荐,内存浪费) - 改用原生 SQL:
Db::raw('COUNT(*)')配合withTrashed() - 最稳妥:用
whereNotNull('delete_time')或where('delete_time', '', null)显式控制条件
示例:
// 统计含软删除的数据总数
$total = Db::name('user')->withTrashed()->count(); // ❌ 仍为 0
// ✅ 正确写法一:显式放开 delete_time 条件
$total = Db::name('user')->where('delete_time', '<>', null)->count();
// ✅ 正确写法二:用 raw 绕过 ORM 聚合限制
$total = Db::name('user')->withTrashed()->field(Db::raw('COUNT(*) as total'))->find()['total'];
ThinkPHP 模型中 withTrashed() 对 select() 有效但对 count() 无效的原因
这不是 Bug,是设计取舍:withTrashed() 只影响 WHERE 条件拼接,而 count() 方法内部会重置查询条件、跳过软删除标记逻辑,以保证「默认行为安全」。
立即学习“PHP免费学习笔记(深入)”;
关键点:
-
withTrashed()修改的是$this->options['where'],但count()会调用独立的buildCountQuery(),不复用该配置 -
onlyTrashed()同理失效,不能依赖它做统计 - 如果用了作用域(scope),需确认是否在 scope 内也调用了
withTrashed()
统计「已删除」和「未删除」数据分别有多少条
别写两个 count() 查询,用单次 GROUP BY 更高效:
$stats = Db::name('user')
->field('COUNT(*) as total, IF(delete_time IS NULL, 0, 1) as is_deleted')
->group('is_deleted')
->select()
->toArray();
// 结果形如:[ ['total' => 120, 'is_deleted' => 0], ['total' => 8, 'is_deleted' => 1] ]
注意:IF() 是 MySQL 函数;SQLite 用 CASE WHEN,PostgreSQL 用 CASE。ThinkPHP 不自动适配这类聚合表达式,得按数据库类型调整。
使用软删除统计时容易忽略的事务与缓存问题
软删除本质是 UPDATE,不是 DELETE,所以:
- 如果用了 Redis 缓存了某个列表总数,软删除后没主动更新缓存,就会显示错误数字
- 在事务中执行
delete()后立刻count(),可能因隔离级别读不到最新delete_time值(尤其 RR 级别) -
restore()后,也要同步修正缓存中的「已删除数」和「总数」
没有银弹方案,必须在业务层明确:统计值是否允许短暂不一致?如果要求强一致,就得加缓存锁或用数据库原子更新(如 UPDATE stats SET deleted_count = deleted_count + 1)。



















