原生SQL全文搜索在Laravel中可行但需绕过Eloquent:MySQL用MATCH...AGAINST+FULLTEXT索引,PostgreSQL用to_tsvector/to_tsquery+GIN索引,二者语法、索引、排序均不兼容,无法跨库抽象。

原生 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 语法,全文搜索靠 tsvector 和 tsquery 类型。用 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 注入。










