如何在Laravel中实现MySQL/PostgreSQL全文搜索? (原生SQL查询)

12次阅读

原生sql全文搜索在laravel中可行但需绕过Eloquent:mysql用MATCH…AGaiNST+FULLTEXT索引,postgresql用to_tsvector/to_tsquery+gin索引,二者语法、索引、排序均不兼容,无法跨库抽象。

如何在Laravel中实现MySQL/PostgreSQL全文搜索? (原生SQL查询)

原生 SQL 全文搜索在 Laravel 中可行,但必须绕过 Eloquent 的查询构建器限制,手动拼接 MATCH ... AGAINST(MySQL)或 @@@(PostgreSQL),且需注意字段类型、索引、参数绑定和方言差异。

MySQL:必须用 MATCH ... AGAINST,不能用 LIKE 替代

MySQL 的全文搜索依赖 FULLTEXT 索引和专用语法。直接写 WHERE title LIKE '%xxx%' 不走全文索引,性能差、不支持相关性排序。

  • FULLTEXT 索引需显式创建(Laravel 迁移中用 $table->fullText('title', 'content')
  • MATCH ... AGAINST 必须与索引字段完全一致,大小写敏感(取决于 collation)
  • 自然语言模式(默认)不支持通配符;布尔模式需加 IN Boolean MODE,且 +/- 不能紧贴引号
  • Laravel 的 DB::select() 支持原生查询,但参数必须用 ? 占位,不能拼接用户输入
DB::select("SELECT *, MATCH(title, content) AGAINST(? IN NATURAL LANGUAGE MODE) AS score            FROM posts             WHERE MATCH(title, content) AGAINST(? IN NATURAL LANGUAGE MODE)            ORDER BY score DESC", ['laravel tutorial', 'laravel tutorial']);

PostgreSQL:用 to_tsvector + to_tsquery,不是 ILIKE

PostgreSQL 没有 MATCH 语法,全文搜索靠 tsvectortsquery 类型。用 ILIKE 或正则模拟会丢失词干提取、停用词过滤和排名能力。

  • 必须先为字段添加 tsvector 列并建立 GIN 索引(如 ALTER table posts ADD column content_search tsvector;
  • 更新时需触发器或应用层调用 to_tsvector('english', title || ' ' || content)
  • plainto_tsquery('english', ?) 更安全(自动处理空格/标点),比 to_tsquery 少出错
  • 排序推荐用 ts_rank,别用 ORDER BY id 掩盖无相关性问题
DB::select("SELECT *, ts_rank(content_search, plainto_tsquery('english', ?)) AS rank            FROM posts             WHERE content_search @@ plainto_tsquery('english', ?)            ORDER BY rank DESC", ['api authentication', 'api authentication']);

数据库兼容?别硬做,选一个主力再适配

Laravel 的 whereFullText() 并不存在,也没有抽象层统一全文语法。强行封装一个“通用全文方法”只会掩盖差异、引入 bug

  • MySQL 的 AGAINST() 返回浮点相关性,PostgreSQL 的 ts_rank() 返回归一化分数,数值不可比
  • PostgreSQL 支持短语搜索(phraseto_tsquery),MySQL 布尔模式需用 "exact phrase",行为不一致
  • 中文需额外处理:MySQL 依赖 ngram 插件,PostgreSQL 需 zhparser 扩展,原生都不支持
  • 如果真要双库支持,建议业务层判断 DB::getDriverName(),分路径执行不同 SQL

参数绑定失效?检查引号和模式修饰符位置

最常见错误是把模式修饰符(如 IN BOOLEAN MODE)写进占位符,或在 AGAINST 里漏掉括号——这会导致 SQL 解析失败,报错类似 SQLSTATE[42000]: Syntax Error

  • AGAINST(? IN BOOLEAN MODE) ✅ —— 修饰符写死,参数只传搜索词
  • AGAINST(?) ✅ —— 默认自然语言模式
  • AGAINST(? IN ? MODE) ❌ —— 第二个 ? 不会被识别为字符串字面量,而是语法错误
  • PostgreSQL 中 plainto_tsquery('english', ?) ✅,但 plainto_tsquery(?, ?) ❌(第一个参数必须是字符串字面量或常量表达式)

字段名、表名、模式名(如 'english')都不能参数化,只能拼接——但务必白名单校验,避免 SQL 注入。

text=ZqhQzanResources