国产数据库全文检索优化实录:告别 LIKE '%关键词%' 的慢查询之痛

达梦数据库的知识库服务用 LIKE '%关键词%' 做模糊查询,数据量一大就全表扫描。从全文索引到反向函数索引,三种方案实测对比。

一条 SQL 拖垮整个搜索体验

知识库服务的文档搜索功能,上线半年后开始频繁触发慢查询告警。定位下来,罪魁祸首是一行 MyBatis Mapper 里的模糊查询:

1
2
3
4
5
6
<select id="searchDocuments" resultType="KnowledgeDocument">
    SELECT * FROM knowledge_document
    WHERE doc_name LIKE CONCAT('%', #{keyword}, '%')
    AND status = 1
    ORDER BY update_time DESC
</select>

LIKE '%关键词%' 这个写法,但凡写过 SQL 的人都知道它走不了 B-Tree 索引。但在数据量只有几千条的时候,全表扫描也就几十毫秒,没人当回事。等到文档数量突破 50 万,P99 响应时间直接飙到 3 秒以上。

现有架构与性能瓶颈

我们的环境是达梦数据库 8.0,知识库服务(knowledge-service)通过 MyBatis 访问。文档表 knowledge_document 的结构大致如下:

字段类型说明
idBIGINT主键
doc_nameVARCHAR(500)文档名称
doc_contentCLOB文档正文
statusINT状态
update_timeTIMESTAMP更新时间

问题链路很清晰:用户输入关键词 → MyBatis 拼出 LIKE '%xxx%' → 达梦优化器判定无法使用普通索引 → 全表扫描 50 万行 → 返回结果。

瓶颈不仅是单次查询慢。高峰期搜索请求并发上来,全表扫描把 Buffer Pool 里的热页全挤出去,连带其他业务查询也跟着变慢。一个搜索功能,变成了整个系统的性能黑洞。

方案一:全文索引(CTXCAT / CONTEXT)

达梦数据库内置了全文检索引擎,支持两种索引类型:

  • CONTEXT:适合长文本(如文档正文),基于分词建立倒排索引,支持 CONTAINS() 语法
  • CTXCAT:适合短文本(如标题、名称),结合结构化字段做混合查询,支持 CATSEARCH() 语法

对于 doc_name 这种短文本字段,CTXCAT 更合适:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- 创建全文索引
CREATE FULLTEXT INDEX idx_doc_name_ft 
ON knowledge_document(doc_name)
LEXER 'CHINESE_LEXER';

-- 查询改写
SELECT * FROM knowledge_document
WHERE CONTAINS(doc_name, #{keyword})
AND status = 1
ORDER BY update_time DESC;

实测结果:50 万文档,关键词查询 P99 从 3200ms 降到 15ms,提升约 200 倍。

注意事项

  • 需要维护索引同步,DML 操作后索引不是实时更新的,可以通过定时任务或触发器做增量同步
  • 中文分词器的选择直接影响搜索质量,达梦自带的 CHINESE_LEXER 基础够用,但遇到行业术语可能需要自定义词典
  • 全文索引会额外占用约原文数据量 30%~50% 的存储空间

方案二:反向函数索引

这是一个巧妙的取巧方案。LIKE 'xxx%' 是可以用索引的(前缀匹配),那把字符串反转过来存,后缀匹配就变成了前缀匹配:

1
2
3
4
5
6
7
8
9
-- 创建反向函数索引
CREATE INDEX idx_doc_name_reverse 
ON knowledge_document(REVERSE(doc_name));

-- 查询改写:搜 "报告" → 反转为 "告报" → 前缀匹配
SELECT * FROM knowledge_document
WHERE REVERSE(doc_name) LIKE CONCAT(REVERSE(#{keyword}), '%')
AND status = 1
ORDER BY update_time DESC;

实测结果:P99 从 3200ms 降到 180ms,提升约 17 倍。

局限性

  • 只解决了"包含"查询中"后缀匹配"的部分场景,对于中间位置的子串匹配效果有限
  • 每次查询需要对关键词做 REVERSE() 转换,应用层需要适配
  • 函数索引在达梦中的维护成本比普通索引高,写入性能有一定损耗

方案三:维持 LIKE + 限定范围

如果不想改索引结构,还有一个折中方案:在 LIKE 基础上增加限定条件,减少扫描范围。

1
2
3
4
5
6
SELECT * FROM knowledge_document
WHERE doc_name LIKE CONCAT('%', #{keyword}, '%')
AND status = 1
AND update_time >= DATEADD(MONTH, -6, CURRENT_TIMESTAMP)
ORDER BY update_time DESC
LIMIT 50;

通过时间范围 + 分页限制,把扫描行数从 50 万压缩到几万。实测 P99 约 800ms,能接受但不理想。

三种方案对比

维度全文索引 (CTXCAT)反向函数索引LIKE + 限定范围
查询性能 (P99)15ms180ms800ms
改造成本中(需改 SQL + 建索引 + 同步机制)低(加索引 + 改 SQL)极低(只改 SQL)
存储开销高(+30%~50%)中(一列索引)
写入影响中(需异步同步)低(函数索引维护)
搜索质量高(支持分词、相关度排序)低(精确子串)低(精确子串)
适用数据量百万级以上十万~百万十万以内

迁移计划与回滚策略

我们选择了全文索引方案,分三个阶段上线:

第一阶段:并行部署(1 周)

  • 在测试环境建全文索引,跑回归测试验证搜索结果一致性
  • 对比 LIKE 查询和 CONTAINS 查询的结果差异,重点验证中文分词效果

第二阶段:灰度切流(1 周)

  • 通过配置开关控制走新查询还是旧查询
  • 先在内部用户灰度 10%,观察搜索准确性和性能指标
  • 确认无误后逐步放量到 100%

第三阶段:清理旧逻辑(1 周后)

  • 移除 MyBatis 中的旧 LIKE 查询代码
  • 配置全文索引定时同步任务(每 5 分钟增量同步)

回滚方案:配置开关一键切回 LIKE 查询,全文索引可以异步删除,不影响线上服务。整个过程对用户透明。

什么时候该上 Elasticsearch?

达梦的全文索引能解决 80% 的搜索优化需求,但以下场景建议直接引入 ES:

  • 需要复杂的相关度排序(TF-IDF、BM25、自定义权重)
  • 多字段联合搜索 + 高亮显示
  • 搜索建议(Suggest)、拼写纠错、同义词扩展
  • 数据量超过千万级,且对搜索延迟要求 < 50ms
  • 需要跨多个异构数据源的统一搜索

全文索引是数据库内置能力的天花板,ES 是专业搜索引擎的起点。如果你的搜索需求已经超出了"能搜到"的范畴,开始追求"搜得准、搜得快、搜得智能",那就别在数据库里折腾了,老老实实搭一套 ES 集群。

对于当前这个知识库场景,50 万文档 + 标题模糊搜索,达梦全文索引已经够用。先解决眼前的性能问题,等需求演进到复杂搜索再考虑架构升级,这才是务实的做法。

广告
广告位预留中 (728x90)