全文搜索的数据库选择:MySQL Fulltext与Elasticsearch的深度对比
一、当"MySQL LIKE"成为全文搜索的唯一工具
某内容平台的搜索功能一直用的是LIKE '%keyword%',在文章数突破10万后,每次搜索耗时3-5秒。运维用MySQL 5.7的Fulltext Index重构了搜索功能,搜索耗时降到了200ms。但新问题接踵而至:中文分词不准("人工智能"被拆成"人工"和"智能")、相关性排序不符合预期(10年前的文章排在第一页)、无法做同义词扩展(搜"AI"不应该漏掉"人工智能"的文章)。
平台最终评估后决定迁移到Elasticsearch,但迁移过程本身是一个6个月的项目。在这个过程中,一个核心问题浮现:哪些场景MySQL Fulltext就够了?哪些必须上ES?
二、两种搜索引擎的底层差异
三、MySQL Fulltext的适用场景与配置
对于中小型内容平台(文章<100万、查询QPS<100),MySQL Fulltext是一个合格的方案:
sql
-- 创建全文索引(ngram分词器处理中文)
ALTER TABLE articles
ADD FULLTEXT INDEX ft_content (title, content)
WITH PARSER ngram;
-- 配置ngram参数
SET GLOBAL ngram_token_size = 2;
-- 搜索
SELECT
article_id, title,
MATCH(title, content) AGAINST('分布式数据库 查询优化' IN BOOLEAN MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST('分布式数据库 查询优化' IN BOOLEAN MODE)
ORDER BY relevance DESC
LIMIT 20;
但Fulltext的边界在于:
python
class MySQLFulltextSearch:
def __init__(self, mysql_pool):
self.mysql = mysql_pool
def search(self, keyword: str, page: int = 1, size: int = 20):
"""MySQL全文搜索(带降级方案)"""
try:
conn = self.mysql.get_connection()
cursor = conn.cursor(dictionary=True)
# 处理特殊字符(防止语法错误)
keyword = self._sanitize_keyword(keyword)
sql = """
SELECT article_id, title,
LEFT(content, 200) AS snippet,
MATCH(title, content) AGAINST(%s IN BOOLEAN MODE) AS score
FROM articles
WHERE MATCH(title, content) AGAINST(%s IN BOOLEAN MODE)
ORDER BY score DESC
LIMIT %s OFFSET %s
"""
cursor.execute(sql, (keyword, keyword, size, (page-1)*size))
results = cursor.fetchall()
# 检查结果质量:如果相关度太低,降级到LIKE
if results and results[0]['score'] < 0.01:
return self._fallback_like_search(keyword, page, size)
return results
except pymysql.OperationalError as e:
if '1191' in str(e): # Fulltext index not found
return self._fallback_like_search(keyword, page, size)
raise SearchException(f"搜索失败: {e}")
finally:
if conn:
conn.close()
def _sanitize_keyword(self, keyword: str) -> str:
"""清理搜索关键词"""
# 移除Fulltext特殊字符
import re
keyword = re.sub(r'[+\-><\(\)~*\"@]', ' ', keyword)
# 添加布尔模式操作符
words = keyword.split()
return ' +'.join([''] + words)
def _fallback_like_search(self, keyword, page, size):
"""Fulltext不可用时的LIKE降级"""
conn = self.mysql.get_connection()
try:
cursor = conn.cursor(dictionary=True)
sql = """
SELECT article_id, title, LEFT(content, 200) AS snippet
FROM articles
WHERE title LIKE %s OR content LIKE %s
LIMIT %s OFFSET %s
"""
like_kw = f'%{keyword}%'
cursor.execute(sql, (like_kw, like_kw, size, (page-1)*size))
return cursor.fetchall()
finally:
conn.close()
四、何时必须升级到Elasticsearch:决策矩阵
| 维度 | MySQL Fulltext OK | 必须上ES |
|---|---|---|
| 数据量 | <100万文档 | >100万文档 |
| 搜索QPS | <100 | >100 |
| 中文分词精度 | 可接受ngram盲切 | 需要词典级分词 |
| 同义词 | 不需要 | 必须(AI=人工智能) |
| 拼音搜索 | 不需要 | 必须 |
| 聚合统计 | 不需要 | 需要(按分类/时间聚合) |
| 多字段权重 | 简单 | 复杂(标题>标签>正文) |
| 搜索建议 | 不需要 | 需要(自动补全) |
| 运维团队 | 无ES经验 | 有ES集群运维能力 |
ES的配置实现(Java客户端):
java
public class ESSearchService {
private final RestHighLevelClient client;
public List<Article> search(String keyword, int page, int size) {
try {
SearchRequest request = new SearchRequest("articles");
// 多字段匹配 + 权重
QueryStringQueryBuilder queryBuilder =
QueryBuilders.queryStringQuery(keyword)
.field("title", 3.0f) // 标题权重3
.field("tags", 2.0f) // 标签权重2
.field("content", 1.0f); // 内容权重1
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
.query(queryBuilder)
.from((page - 1) * size)
.size(size)
.highlight(new HighlightBuilder()
.field("content")
.fragmentSize(200)
.numOfFragments(1));
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
return parseHits(response);
} catch (ElasticsearchStatusException e) {
if (e.status() == RestStatus.NOT_FOUND) {
return Collections.emptyList();
}
throw new SearchException("ES搜索异常", e);
} catch (IOException e) {
// ES不可用时降级到MySQL
return mysqlFallback.search(keyword, page, size);
}
}
}
五、总结
MySQL Fulltext和Elasticsearch不是竞争关系,而是数据量和技术栈的演进路径。初创内容平台在MySQL Fulltext上可以跑到100万文档、100QPS,这个阶段的维护成本为零。当搜索需求开始涉及中文语义、同义词、智能排序时,ES的代价(独立集群、调优、监控)才开始变得值得。
一条简单的决策线:如果你的搜索需求可以用布尔匹配模式描述("包含A且不包含B"),MySQL Fulltext足够。如果需要"用户搜AI时也能找到人工智能相关的文章",那就是ES的主场了。
本文属于「行业场景与项目复盘」系列,深入对比MySQL Fulltext与Elasticsearch在不同规模下的选型决策。