match() against()是唯一能真正提速多关键词模糊匹配的原生方案,因fulltext采用倒排索引,跳过不含关键词的行,避免like '%keyword%'的全表扫描。

直接用 MATCH() AGAINST() 替代 LIKE '%keyword%',是唯一能真正提速多关键词模糊匹配的原生方案。其他写法——包括 INSTR()、REGEXP、甚至带函数的 LIKE——在大数据量下都逃不开全表扫描。
为什么MATCH AGAINST比LIKE快得多
FULLTEXT 用的是倒排索引,不是 B+ 树。它把字段内容切分成词(token),每个词直接映射到包含它的行 ID;查 MATCH(name) AGAINST('张三') 时,MySQL 跳过所有不含“张三”的行,不逐行扫描字符串。而 LIKE '%张三%' 即便字段有普通索引,也完全无法利用——B+ 树只支持前缀匹配,对中间或后缀无效。
实测:100 万行用户表中搜“张三”,LIKE 平均耗时 2.3s,MATCH AGAINST 在布尔模式下稳定在 0.04s 左右。
多关键词必须用布尔模式 + 操作符
自然语言模式(NATURAL LANGUAGE MODE)会自动做相关性排序,但不保证每个词都出现;要实现“张三 AND 李四 AND 北京”这种严格交集,必须用布尔模式,并显式加 +:
-
MATCH(name, snippet) AGAINST('+张三 +李四 +北京' IN BOOLEAN MODE)—— 三个词都必须存在 -
MATCH(name) AGAINST('+张三 -李四' IN BOOLEAN MODE)—— 含“张三”但不含“李四” -
MATCH(name) AGAINST('张*' IN BOOLEAN MODE)——*只能在词尾,且仅布尔模式生效;'*张'或'张*三'会报ERROR 1064
注意:AGAINST() 的第一个参数必须是字符串字面量,不能是变量拼接(如 CONCAT('+', @kw)),否则语法错误或索引失效。
中文多关键词搜索的两个硬门槛
MySQL 默认 FULLTEXT 对中文基本不可用,卡在两个地方:
-
innodb_ft_min_token_size默认是 3,单字词(如“张”“三”)直接被丢弃;需改配置并重建索引:SET GLOBAL innodb_ft_min_token_size = 1;,然后ALTER TABLE users DROP INDEX ft_name, ADD FULLTEXT ft_name (name); - 没有分词能力,“张三丰”会被当做一个完整 token,搜“张三”就匹配不到;推荐用
ngram解析器建索引:CREATE FULLTEXT INDEX idx_name_ngram ON users(name) WITH PARSER ngram;
停用词(如“的”“了”)也会被过滤,查不到结果先确认是否在 INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD 里;别急着删索引,先查停用词表。
全文索引不是加了就生效,这些条件缺一不可
以下任意一条不满足,MATCH AGAINST 就退化为全表扫描:
- 表引擎必须是
InnoDB(5.6+)或MyISAM;用SHOW CREATE TABLE users确认,不是就先ALTER TABLE users ENGINE = INNODB - 字段类型只能是
CHAR、VARCHAR、TEXT;INT或JSON类型加了索引也没用 - 查询必须用
MATCH(col) AGAINST(...),混用LIKE、=或子查询都会让全文索引失效 - 布尔模式下,
+和-后面不能跟空格,'+ 张三'是错的,必须写成'+张三'
最常被忽略的一点:全文索引对极短词(如“AI”“OK”)默认不收录,改 innodb_ft_min_token_size 是全局行为,会影响所有表;生产环境建议优先走应用层预分词(比如用 jieba 把“张三丰”拆成“张三丰 张三 三丰”再存进新字段),更可控。











