如何让Laravel Scout数据库驱动为FULLTEXT索引搜索添加后缀通配符?
问题描述
不想依赖Elasticsearch、Algolia这类专用搜索API,打算通过MySQL的FULLTEXT索引提升搜索性能,但遇到以下问题:
- 搜索单词的部分内容(例如输入8字符单词中的7个)时,始终返回0条结果,仅能匹配完整单词
- 对比手动执行的查询发现,Laravel Scout生成的SQL未给关键词添加末尾通配符
*,手动添加后可得到预期结果,且性能优于WHERE LIKE "%...%"
需求:让Laravel Scout自动为搜索关键词添加末尾通配符,适配MySQL FULLTEXT的布尔模式搜索。
已做配置:
- 为Products表的
page_title字段添加FULLTEXT索引 - Product模型配置如下:
/** * Get the indexable data array for the model. * * @return array<string, mixed> */ #[SearchUsingPrefix(['code'])] #[SearchUsingFullText(['page_title'])] public function toSearchableArray() { return [ 'page_title' => $this->page_title, 'code' => $this->code, ]; }
- 尝试添加
#[SearchUsingPrefix(['page_title'])],无效果
测试场景:
- 使用Scout搜索:输入"fitte"(目标匹配"fitted")返回0条结果
Product::search($q)->query(function($query){ $query->select(['id', 'page_title as text', 'code as subtext'])->limit(5); })->get() - 手动执行FULLTEXT布尔模式查询:相同关键词可得到预期结果
Product::whereRaw("MATCH(page_title) AGAINST('$q*' IN BOOLEAN MODE)") ->select(['id', 'page_title as text', 'code as subtext'])->limit(5) ->get()
环境:XAMPP运行MySQL
解决方法
方法1:手动处理搜索关键词
在调用Scout的search方法前,直接给关键词添加末尾通配符。如果是多关键词,需为每个词单独加*:
// 单关键词 $q = $request->input('q') . '*'; // 多关键词(拆分后逐个加*) $q = collect(explode(' ', $request->input('q'))) ->map(fn($word) => $word . '*') ->implode(' '); // 执行搜索 Product::search($q)->query(function($query){ $query->select(['id', 'page_title as text', 'code as subtext'])->limit(5); })->get();
方法2:自定义Scout搜索Builder
通过继承默认Builder类,全局修改搜索关键词处理逻辑:
- 创建自定义Builder类
namespace App\Scout; use Laravel\Scout\Builder; class MySqlFulltextBuilder extends Builder { public function buildQuery() { // 仅针对FULLTEXT字段的搜索添加通配符 if ($this->query && property_exists($this->model, 'searchableFullTextColumns')) { // 为每个关键词添加末尾* $this->query = preg_replace('/(\S+)/', '$1*', $this->query); } return parent::buildQuery(); } }
- 在Product模型中重写
search方法,使用自定义Builder
use App\Scout\MySqlFulltextBuilder; public static function search($query = '', $callback = null) { return new MySqlFulltextBuilder(new static, $query, $callback); }
方法3:自定义MySQL Scout驱动
如果需要全局适配所有模型的FULLTEXT搜索,可自定义驱动:
- 创建自定义引擎类
namespace App\Scout; use Laravel\Scout\Engines\MySqlEngine; use Laravel\Scout\Builder; class CustomMySqlEngine extends MySqlEngine { public function search(Builder $builder) { // 处理关键词,为每个词添加末尾* if ($builder->query) { $builder->query = preg_replace('/(\w+)/', '$1*', $builder->query); } return parent::search($builder); } }
- 在
config/scout.php中替换默认MySQL引擎
'engines' => [ // 其他引擎... 'mysql' => App\Scout\CustomMySqlEngine::class, ],
关于#[SearchUsingPrefix]无效的原因
#[SearchUsingPrefix]注解是Scout用来生成LIKE 'xxx%'前缀匹配查询的,仅适用于普通字段的前缀搜索,并不适配MySQL FULLTEXT索引的布尔模式通配符逻辑,因此对page_title这类FULLTEXT字段无效。
内容的提问来源于stack exchange,提问作者Mateusz Sromala
相关产品推荐
相关产品推荐

