软删除性能依赖索引与正确写法:delete_time字段必须建联合索引(如INDEX(status, delete_time)),且查询须用whereNull()而非= NULL;关联预载入时field()需显式包含外键和delete_time字段。

软删除本身不拖慢查询,但没配索引或写错条件时,会把性能拖垮。
软删除字段必须加索引
ThinkPHP 的 delete_time(或你自定义的软删字段)默认不会被建索引。一旦在 where 条件里用到它,比如 where('delete_time', null) 或 whereNull('delete_time'),数据库就会全表扫描——哪怕表只有 1 万行,也可能从 5ms 涨到 300ms。
- MySQL 不支持对
NULL值高效索引,所以INDEX(delete_time)效果有限;更稳妥的是建联合索引,例如INDEX(status, delete_time)(如果你常查status = 1 AND delete_time IS NULL) - TP 自带的
withTrashed()、onlyTrashed()方法底层仍走WHERE delete_time IS NOT NULL这类条件,没索引一样扫全表 - 迁移文件里加索引示例:
$table->index(['delete_time', 'status']);
where() 里别直接写 delete_time = null
手写 where('delete_time', null) 在 TP 里会被转成 = NULL,而 SQL 中 = NULL 永远为 false,实际查不到数据;必须用 whereNull('delete_time') 或 where('delete_time', 'IS', null) 才生成正确的 IS NULL。
- 错误写法:
UserModel::where('delete_time', null)->select()→ 生成WHERE delete_time = NULL,查不到任何记录 - 正确写法:
UserModel::whereNull('delete_time')->select()→ 生成WHERE delete_time IS NULL - 如果字段名不是默认的
delete_time,记得在模型里配置protected $deleteTime = 'removed_at';,否则whereNull()会找错字段
软删除 + 关联预载入时 field() 容易漏外键
启用软删除后,关联模型如果也用了软删,且你在 with() 闭包里写了 field(),很容易漏掉外键或 delete_time 字段,导致关联数据绑定失败、返回空数组。
立即学习“PHP免费学习笔记(深入)”;
- 例如:
User::with(['posts' => fn($q) => $q->field('title, content')])->find(123),如果posts表有软删字段delete_time且没加进field(),TP 就无法判断哪些 post 是“有效”的,可能把已软删的也混进来,或干脆不绑定 - 安全做法:只要关联表启用了软删除,
field()至少带上id、外键(如user_id)、软删字段(如delete_time) - 示例:
->field('id, user_id, title, delete_time')
软删除不是开关一开就完事,它让查询多了一层逻辑判断,而这一层判断是否高效,完全取决于你有没有在数据库和代码里同步补上对应约束。最常被忽略的是联合索引顺序和 field() 中软删字段的显式声明。



















