Laravel如何利用Eloquent实现全文检索?(高效方案)
Laravel通过Eloquent结合MySQL全文索引实现检索,需添加FULLTEXT索引,使用whereRaw调用MATCH...AGAINST。MySQL5.6+支持InnoDB全文索引,BOOLEAN模式更灵活。Scout+MySQL驱动简化操作,但需重写toSearchableArray并手动更新索引。中文分词受限于MySQL。
先从 match() against() 说起。这确实是最稳的起点,不需要一开始就部署 Scout 或 Algolia 这类额外服务。Lara vel 默认没有直接暴露 MySQL 全文索引的支持,但原生 SQL 的能力足够撑起一套可用的方案,而且没有外部依赖,维护成本也低。
很多人踩过的坑,是建表时忘了加 FULLTEXT 索引,或者字段类型不匹配——TEXT 和 VARCHAR 是安全的,但 JSON 字段不行,那是另一个故事了。
- 迁移中加入索引:
DB::statement("ALTER TABLE posts ADD FULLTEXT(title, content)"); - Eloquent 查询时,用
whereRaw调用:Post::whereRaw("MATCH(title, content) AGAINST(? IN NATURAL LANGUAGE MODE)", [$keyword])->get(); - 有一点必须提醒:MySQL 5.6+ 才开始支持 InnoDB 的全文索引;MyISAM 虽然也支持,但已逐渐被淘汰,别用了。
IN NATURAL LANGUAGE MODE对短词(比如“php”)效果很差,搜不到的话换IN BOOLEAN MODE,它支持+、-、*这类通配操作,灵活得多。

Lara vel Scout + mysql 驱动能省事,但得改默认行为
Scout 默认监听 created、updated、deleted 这些事件,但全文检索的常用写法是让你把 title 和 content 合成一个 searchableAs() 字段,否则搜不到。这个设计本身没问题,问题是很多人没重写这个默认行为。
- 重写模型的
toSearchableArray():return ['title' => $this->title, 'content' => $this->content]; - 首次或结构变更后,需要手动触发索引更新:
php artisan scout:import "App\Models\Post",不跑这一步,新的内容就喂不进去。 mysql驱动本质上还是走match() against(),只是帮你自动建索引、拼 SQL,适合不想手写 raw 语句的人。- 性能上没有本质提升,反而多了一层抽象;并发高的时候要留意
queue的配置,否则搜索结果会出现延迟。
中文分词问题不是 Lara vel 的锅,是 MySQL 的限制
MySQL 的全文索引对中文的支持其实非常有限——它按空格和标点切词,中文偏偏没有这种天然分隔。搜“数据库优化”大概率失败,除非你在写入时就手动用空格隔开“数据 库 优 化”。
- 一种临时解法:用
IN BOOLEAN MODE+ 星号通配,比如"数据库*",但精度低,容易误召回。 - 更务实的做法:前端搜索前做一次简单分词(比如用
preg_split('/(? 拆成单字),然后拼成+"数" +"据" +"库"这种格式,虽然粗暴,但在小项目中够用。 - 真正需要高质量中文搜索时,就得换引擎(MeiliSearch 或 Typesense)。Scout 支持无缝切换,但部署成本会立刻上升。
- 别信“改
ft_min_word_len就行”的说法——那是针对英文最小词长的,对中文一点用都没有。
whereFullText() 不是 Eloquent 内置方法,别被文档误导
网上很多文章提到 Post::whereFullText('title', $q)->get() —— 假的。Eloquent 压根没有这个方法。要么是自定义的 Scope,要么是 Scout 扩展,要么纯粹是抄错代码。
- 翻一下 Eloquent Builder 源码就清楚了,里面根本没定义
whereFullText,Lara vel 官方也从未提供过这个方法。 - 如果有人用了,八成是封装了
whereRaw("MATCH(...) AGAINST(...)")的本地 Scope。 - 想自己加也可以,但要注意参数注入安全:
protected static function booted() { static::addGlobalScope('fulltext', new FullTextScope()); }是一种写法。 - 更轻量的做法,直接在模型里写一个作用域方法:
public function scopeWhereFullText($query, $columns, $keyword) { ... },干净、可控。
说到底,真正卡住人的从来不是语法,而是 MySQL 全文索引那些隐式的行为:停用词表、最小词长、布尔模式下引号和括号的优先级。调一次 SELECT @@ft_stopword_file, @@ft_min_word_len,比翻三页技术文档都有用。


































