软删除字段不影响全文索引本身,但会导致已软删记录仍出现在搜索结果中;必须在查询时显式添加 WHERE deleted_at IS NULL 条件,并在 Scout 中重写 shouldBeSearchable() 和 toSearchableArray(),同时 PostgreSQL 需使用部分索引。

软删除字段怎么影响全文索引查询
默认情况下,Laravel 的 softDeletes() 会在模型加一个 deleted_at 字段,但数据库全文索引(比如 MySQL 的 FULLTEXT 或 PostgreSQL 的 to_tsvector)完全不认这个字段——它只管数据存不存在,不管是不是“逻辑删了”。结果就是:已软删的记录仍会出现在全文搜索结果里。
MySQL FULLTEXT 索引下如何排除软删记录
MySQL 不支持在 FULLTEXT 索引定义里写 WHERE 条件,所以不能靠建索引时过滤。必须在查询层动手:
- 全文搜索用
MATCH ... AGAINST,但一定要显式加WHERE deleted_at IS NULL - 别用
whereRaw('MATCH(...)')单独拼条件,容易漏掉软删判断 - 如果用了 Laravel Scout + TNTSearch / Meilisearch,得重写
toSearchableArray(),主动过滤掉deleted_at !== null的模型 - 注意:MySQL 5.6+ 的布尔模式(
IN BOOLEAN MODE)不改变这一逻辑,仍需手动 WHERE 过滤
Laravel Scout 配合软删除的坑
Scout 默认同步所有模型到搜索引擎,包括 deleted_at 非空的——这会导致已删记录继续被搜到,甚至可能因数据不一致引发 404 或权限异常。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
- 在模型里加
protected $casts = ['deleted_at' => 'datetime'];,否则 Scout 可能序列化出错 - 重写
shouldBeSearchable()方法,返回$this->deleted_at === null - 执行
php artisan scout:flush "App\Models\Post"清空旧索引后,再scout:import,否则残留数据还在 - TNTSearch 本地引擎对 NULL 判断敏感,用
=== null比== null更安全
PostgreSQL 全文检索 + 软删除怎么配
PostgreSQL 的 to_tsvector 和 @@ 操作符本身无状态,但触发器或物化视图若没考虑 deleted_at,就会把软删数据也编进向量里。
- 建自定义 GIN 索引时,不能直接
CREATE INDEX ... ON posts USING GIN (to_tsvector('chinese', title || ' ' || content));—— 必须配合部分索引 - 正确做法是:
CREATE INDEX idx_posts_search_active ON posts USING GIN (to_tsvector('chinese', title || ' ' || content)) WHERE deleted_at IS NULL; - 查询时也要保持 WHERE 一致:
WHERE deleted_at IS NULL AND to_tsvector('chinese', title || ' ' || content) @@ to_tsquery('chinese', 'xxx'); - 用 Eloquent 查询时,记得调用
withTrashed()或onlyTrashed()是绕过软删逻辑的,别误用在搜索场景里
最麻烦的不是加条件,而是所有涉及搜索的入口——API、后台管理、队列任务里的导入逻辑——都得统一检查是否过滤了 deleted_at。漏一处,就有一处脏数据冒出来。










