mysql fulltext索引需预先创建,否则match()报错;innodb要求5.6+,myisam原生支持;中文需预分词或换用es等引擎,原生不支持中文分词。

MySQL FULLTEXT索引必须先建好,否则MATCH()报错
直接写 MATCH(title, content) AGAINST('关键词') 却没建索引,MySQL 会抛出错误:ERROR 1191: Can't find FULLTEXT index matching the column list。这不是语法问题,是结构缺失。
建索引前先确认引擎:InnoDB 表需 MySQL 5.6+,MyISAM 则一直支持。执行以下语句(字段名按实际调整):
ALTER TABLE articles ADD FULLTEXT(title, content);
- 不能对单个 TEXT 字段单独加 FULLTEXT;必须显式指定字段列表
- 若表已有大量数据,
ALTER TABLE可能锁表较久,建议在低峰期操作 - 中文字段要特别注意:默认最小词长为 4,像“PHP”“AI”这类短词搜不到,需修改配置
ft_min_word_len = 2并重启 MySQL(且重建索引)
PHP中必须用预处理防止布尔模式注入
AGAINST() 的第二个参数支持用户可控的模式(如 IN BOOLEAN MODE),如果把未过滤的用户输入拼进查询字符串,攻击者可构造 +abc -def "sql injection" 来绕过逻辑或干扰排序。
正确做法是只让 PHP 控制模式类型,搜索词走参数绑定:
$keyword = $_GET['q'] ?? '';<br>$mode = 'IN NATURAL LANGUAGE MODE'; // 或硬编码为 'IN BOOLEAN MODE'<br>$sql = "SELECT *, MATCH(title, content) AGAINST(? $mode) AS score<br> FROM articles WHERE MATCH(title, content) AGAINST(? $mode)";<br>$stmt = $pdo->prepare($sql);<br>$stmt->execute([$keyword, $keyword]);
- 不要用
sprintf或字符串拼接$mode,哪怕它看起来“固定”——万一将来加开关就埋雷 - 布尔模式下,
+和-操作符对空格敏感,用户输+php -laravel有效,但+ php - laravel会失效 - 自然语言模式不支持操作符,强行加
+会被忽略,不会报错但结果意外
中文搜索不准?不是MATCH的问题,是分词没做
MySQL 原生 FULLTEXT 对中文不做分词,把整段文字当一个超长词处理。例如字段存“手机防水功能强大”,搜“防水”基本不命中,因为底层索引里没有“防水”这个独立词条。
可行解法只有两个方向:
- 在写入时预分词:用
jieba-php或SCWS把标题/内容切好,存入额外字段(如title_tokens),再对这个字段建 FULLTEXT 索引 - 改用外部搜索引擎:Elasticsearch + IK 分词插件、Meilisearch 或 Typesense,它们原生支持中文粒度控制
- 临时 workaround:用
LIKE '%关键词%'配合CONCAT()拼接字段,但性能差、无法排序、不支持停用词
别信“改 ft_stopword_file 就能解决中文”的说法——停用词只是过滤,不分词还是白搭。
相关性得分 score 不稳定?检查字段权重和长度
MATCH() AGAINST() 返回的 score 是浮点数,但它的值受字段内容长度、重复次数、是否跨字段匹配等影响,相同关键词在不同记录中得分可能差异很大,甚至出现长文本得分反而更低的情况。
想让排序更合理,可以:
- 显式加
ORDER BY score DESC,否则 MySQL 不保证顺序 - 避免在
MATCH()中混用长短字段(比如MATCH(title, content)),title 短但权重高,content 长但稀释得分;可拆成两个独立 MATCH 计算后加权 - 短文本场景下,自然语言模式比布尔模式更易获得有区分度的 score;布尔模式的 score 基本恒为 1
- 如果只想要“是否匹配”,不用 score,布尔模式更快,也更节省 CPU
真正难的是让“手机”和“智能手机”语义接近——MySQL 做不到,得上同义词库或向量检索,那是另一层复杂度了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











