全文搜索的数据库选择:MySQL Fulltext与Elasticsearch的深度对比

📅 2026/7/24 15:47:18
全文搜索的数据库选择:MySQL Fulltext与Elasticsearch的深度对比
全文搜索的数据库选择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万、查询QPS100MySQL Fulltext是一个合格的方案-- 创建全文索引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的边界在于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(dictionaryTrue) # 处理特殊字符防止语法错误 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(dictionaryTrue) 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万文档搜索QPS100100中文分词精度可接受ngram盲切需要词典级分词同义词不需要必须AI人工智能拼音搜索不需要必须聚合统计不需要需要按分类/时间聚合多字段权重简单复杂标题标签正文搜索建议不需要需要自动补全运维团队无ES经验有ES集群运维能力ES的配置实现Java客户端public class ESSearchService { private final RestHighLevelClient client; public ListArticle 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且不包含BMySQL Fulltext足够。如果需要用户搜AI时也能找到人工智能相关的文章那就是ES的主场了。本文属于「行业场景与项目复盘」系列深入对比MySQL Fulltext与Elasticsearch在不同规模下的选型决策。