开场白

做搜索功能的时候,很多人第一反应是 LIKE ‘%关键词%’,数据量小的时候没问题,数据一大直接全表扫描。我之前有个项目,商品表的 LIKE 搜索在 50 万条数据时就要 3 秒以上,根本没法用。后来上了全文索引,查询时间降到毫秒级。不过全文索引也有自己的坑——中文分词、停用词、最小搜索长度,这些搞不明白一样用不好。

全文索引基础

创建全文索引

-- 建表时创建
CREATE TABLE articles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
        title VARCHAR(200),
            content TEXT,
                FULLTEXT INDEX ft_title_content (title, content)
                );
-- 已有表添加
ALTER TABLE articles ADD FULLTEXT INDEX ft_title_content (title, content);

-- 单列全文索引
ALTER TABLE articles ADD FULLTEXT INDEX ft_content (content);

基本查询

-- 自然语言模式(默认)
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('数据库优化');

-- 布尔模式
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('+MySQL -Oracle' IN BOOLEAN MODE);

-- 查询扩展模式
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('数据库' WITH QUERY EXPANSION);

MATCH 里的列必须和全文索引定义的列完全一致,顺序也要一样。如果索引是 (title, content),查询必须写 MATCH(title, content),不能写 MATCH(content, title)

三种搜索模式

自然语言模式

默认模式,按相关性排序:

SELECT *, MATCH(title, content) AGAINST('数据库优化') AS score
FROM articles 
WHERE MATCH(title, content) AGAINST('数据库优化')
ORDER BY score DESC;

相关性分数基于词频(TF)和逆文档频率(IDF),MySQL 内部计算,不需要我们操心。

搜索的是"数据库"和"优化"两个词的 OR 组合——包含任意一个词的记录都会返回。

布尔模式

精确控制搜索逻辑:

-- 必须包含 MySQL,不能包含 Oracle
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('+MySQL -Oracle' IN BOOLEAN MODE);

-- 必须同时包含 MySQL 和 优化
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('+MySQL +优化' IN BOOLEAN MODE);

-- 包含 MySQL 或 Oracle
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('MySQL Oracle' IN BOOLEAN MODE);

-- 包含以数据开头的词
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('数据*' IN BOOLEAN MODE);

-- 必须包含 MySQL,且优化权重更高
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('+MySQL >优化' IN BOOLEAN MODE);

布尔模式的操作符:

操作符 含义
+ 必须包含
- 不能包含
可选,包含则加分
* 通配符(只能后缀)
“” 短语匹配
> < 增减权重
() 分组
~ 取反(降低相关性)

短语匹配要特别注意:

-- 精确匹配"数据库优化"这个短语
SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('"数据库优化"' IN BOOLEAN MODE);

查询扩展模式

两步搜索:先搜一次,用结果中的词再搜一次。适合用户搜索词太少、结果不够的情况。

SELECT * FROM articles 
WHERE MATCH(title, content) AGAINST('数据库' WITH QUERY EXPANSION);

但这个模式容易返回不相关的结果,慎用。

中文分词问题

这是全文索引最大的坑。MySQL 默认的分词器按空格和标点分词,中文没有空格,整段中文会被当成一个词:

-- content = "MySQL数据库优化实战"
-- 默认分词器会把"MySQL数据库优化实战"当成一个整体词
-- 搜"数据库"搜不到!
SELECT * FROM articles WHERE MATCH(content) AGAINST('数据库');

解决方案一:ngram 分词器(MySQL 5.7.6+)

-- 建表时指定 ngram 分词器
CREATE TABLE articles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
        title VARCHAR(200),
            content TEXT,
                FULLTEXT INDEX ft_title_content (title, content) WITH PARSER ngram
                );
-- 添加索引时指定
ALTER TABLE articles ADD FULLTEXT INDEX ft_content (content) WITH PARSER ngram;

ngram 分词器按固定长度把文本切成片段,默认 n=2(二元分词):

"数据库优化" → "数据", "据库", "库优", "优化"

ngram 的 n 值通过 ngram_token_size 参数配置,范围 1-10,默认 2。改这个参数需要重启 MySQL:

# my.cnf
[mysqld]
ngram_token_size = 2

n=2 意味着搜索词至少 2 个字符才能命中。搜单个字(比如"数")搜不到。

解决方案二:手动分词

在插入数据前,用应用层分词器(比如 IK、jieba)把中文文本切成词,用空格拼接后存储:

-- 原文:"MySQL数据库优化实战"
-- 分词后存储:"MySQL 数据库 优化 实战"
INSERT INTO articles (title, content) VALUES (
    'MySQL数据库优化实战',
        'MySQL 数据库 优化 实战'
        );
        ```
这种方式灵活,分词质量高,但增加了应用层复杂度。

## 全文索引的配置参数

### ft_min_word_len / innodb_ft_min_token_size

最小索引词长度,默认 InnoDB 是 3,MyISAM 是 4。长度小于这个值的词不会被索引。

```ini
# my.cnf
[mysqld]
innodb_ft_min_token_size = 1  # InnoDB
ft_min_word_len = 1            # MyISAM

改成 1 之后需要重建全文索引:

ALTER TABLE articles DROP INDEX ft_content;
ALTER TABLE articles ADD FULLTEXT INDEX ft_content (content) WITH PARSER ngram;

innodb_ft_cache_size

全文索引缓存大小,默认 32MB。批量插入大量数据时,增大这个值可以提升索引构建速度。

ft_query_expansion_limit

查询扩展模式的最大扩展词数,默认 20。

全文索引 vs LIKE

维度 全文索引 LIKE
索引利用 走全文索引 前缀匹配走索引,%xx%全表扫描
搜索精度 分词匹配,支持相关性排序 模糊匹配,无排序
中文支持 需要 ngram 或手动分词 直接支持
写入性能 需要维护索引,写入较慢 无额外开销
存储开销 索引占额外空间 无额外空间

简单说:精确匹配用 LIKE,搜索功能用全文索引。

实战建议

  1. 搜索场景用全文索引,匹配场景用 LIKE——别拿全文索引当万能的
    1. 中文必须用 ngram 分词器——否则搜不到东西
    1. 布尔模式的 + 和 - 组合最实用——精确控制搜索结果
    1. 注意最小搜索长度——ngram_token_size=2 时搜单个字搜不到
    1. 全文索引会降低写入性能——不适合频繁更新的表
    1. 数据量特别大时考虑 Elasticsearch——MySQL 全文索引有上限,亿级数据还是上 ES 吧

小结

全文索引是 MySQL 内置的搜索方案,适合中小规模的搜索需求。关键点就三条:中文用 ngram 分词器,查询用布尔模式精确控制,搜索长度注意最小值限制。如果你的搜索需求很复杂(拼音搜索、同义词、高亮等),还是得上 Elasticsearch,MySQL 全文索引搞不定。

相关阅读

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐