<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>国产数据库 on 文艺技术笔记</title><link>https://wenyiblog.top/tags/%E5%9B%BD%E4%BA%A7%E6%95%B0%E6%8D%AE%E5%BA%93/</link><description>Recent content in 国产数据库 on 文艺技术笔记</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>文艺技术笔记 | 软件工程师文艺</copyright><lastBuildDate>Thu, 18 Jun 2026 22:25:00 +0800</lastBuildDate><atom:link href="https://wenyiblog.top/tags/%E5%9B%BD%E4%BA%A7%E6%95%B0%E6%8D%AE%E5%BA%93/index.xml" rel="self" type="application/rss+xml"/><item><title>国产数据库全文检索优化实录：告别 LIKE '%关键词%' 的慢查询之痛</title><link>https://wenyiblog.top/2026/06/dameng-fulltext-search-optimization/</link><pubDate>Thu, 18 Jun 2026 22:25:00 +0800</pubDate><guid>https://wenyiblog.top/2026/06/dameng-fulltext-search-optimization/</guid><description>&lt;h2 id="一条-sql-拖垮整个搜索体验"&gt;&lt;a href="#%e4%b8%80%e6%9d%a1-sql-%e6%8b%96%e5%9e%ae%e6%95%b4%e4%b8%aa%e6%90%9c%e7%b4%a2%e4%bd%93%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;一条 SQL 拖垮整个搜索体验
&lt;/h2&gt;&lt;p&gt;知识库服务的文档搜索功能，上线半年后开始频繁触发慢查询告警。定位下来，罪魁祸首是一行 MyBatis Mapper 里的模糊查询：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;searchDocuments&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;resultType=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;KnowledgeDocument&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SELECT * FROM knowledge_document
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; WHERE doc_name LIKE CONCAT(&amp;#39;%&amp;#39;, #{keyword}, &amp;#39;%&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; AND status = 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ORDER BY update_time DESC
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;LIKE '%关键词%'&lt;/code&gt; 这个写法，但凡写过 SQL 的人都知道它走不了 B-Tree 索引。但在数据量只有几千条的时候，全表扫描也就几十毫秒，没人当回事。等到文档数量突破 50 万，P99 响应时间直接飙到 3 秒以上。&lt;/p&gt;
&lt;h2 id="现有架构与性能瓶颈"&gt;&lt;a href="#%e7%8e%b0%e6%9c%89%e6%9e%b6%e6%9e%84%e4%b8%8e%e6%80%a7%e8%83%bd%e7%93%b6%e9%a2%88" class="header-anchor"&gt;&lt;/a&gt;现有架构与性能瓶颈
&lt;/h2&gt;&lt;p&gt;我们的环境是达梦数据库 8.0，知识库服务（knowledge-service）通过 MyBatis 访问。文档表 &lt;code&gt;knowledge_document&lt;/code&gt; 的结构大致如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;BIGINT&lt;/td&gt;
&lt;td&gt;主键&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;doc_name&lt;/td&gt;
&lt;td&gt;VARCHAR(500)&lt;/td&gt;
&lt;td&gt;文档名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;doc_content&lt;/td&gt;
&lt;td&gt;CLOB&lt;/td&gt;
&lt;td&gt;文档正文&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status&lt;/td&gt;
&lt;td&gt;INT&lt;/td&gt;
&lt;td&gt;状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;update_time&lt;/td&gt;
&lt;td&gt;TIMESTAMP&lt;/td&gt;
&lt;td&gt;更新时间&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;问题链路很清晰：用户输入关键词 → MyBatis 拼出 &lt;code&gt;LIKE '%xxx%'&lt;/code&gt; → 达梦优化器判定无法使用普通索引 → 全表扫描 50 万行 → 返回结果。&lt;/p&gt;
&lt;p&gt;瓶颈不仅是单次查询慢。高峰期搜索请求并发上来，全表扫描把 Buffer Pool 里的热页全挤出去，连带其他业务查询也跟着变慢。一个搜索功能，变成了整个系统的性能黑洞。&lt;/p&gt;
&lt;h2 id="方案一全文索引ctxcat--context"&gt;&lt;a href="#%e6%96%b9%e6%a1%88%e4%b8%80%e5%85%a8%e6%96%87%e7%b4%a2%e5%bc%95ctxcat--context" class="header-anchor"&gt;&lt;/a&gt;方案一：全文索引（CTXCAT / CONTEXT）
&lt;/h2&gt;&lt;p&gt;达梦数据库内置了全文检索引擎，支持两种索引类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CONTEXT&lt;/strong&gt;：适合长文本（如文档正文），基于分词建立倒排索引，支持 &lt;code&gt;CONTAINS()&lt;/code&gt; 语法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CTXCAT&lt;/strong&gt;：适合短文本（如标题、名称），结合结构化字段做混合查询，支持 &lt;code&gt;CATSEARCH()&lt;/code&gt; 语法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于 &lt;code&gt;doc_name&lt;/code&gt; 这种短文本字段，CTXCAT 更合适：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 创建全文索引
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FULLTEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_doc_name_ft&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;knowledge_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;LEXER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CHINESE_LEXER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 查询改写
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;knowledge_document&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONTAINS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;update_time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;实测结果&lt;/strong&gt;：50 万文档，关键词查询 P99 从 3200ms 降到 15ms，提升约 200 倍。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意事项&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要维护索引同步，DML 操作后索引不是实时更新的，可以通过定时任务或触发器做增量同步&lt;/li&gt;
&lt;li&gt;中文分词器的选择直接影响搜索质量，达梦自带的 &lt;code&gt;CHINESE_LEXER&lt;/code&gt; 基础够用，但遇到行业术语可能需要自定义词典&lt;/li&gt;
&lt;li&gt;全文索引会额外占用约原文数据量 30%~50% 的存储空间&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="方案二反向函数索引"&gt;&lt;a href="#%e6%96%b9%e6%a1%88%e4%ba%8c%e5%8f%8d%e5%90%91%e5%87%bd%e6%95%b0%e7%b4%a2%e5%bc%95" class="header-anchor"&gt;&lt;/a&gt;方案二：反向函数索引
&lt;/h2&gt;&lt;p&gt;这是一个巧妙的取巧方案。&lt;code&gt;LIKE 'xxx%'&lt;/code&gt; 是可以用索引的（前缀匹配），那把字符串反转过来存，后缀匹配就变成了前缀匹配：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 创建反向函数索引
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_doc_name_reverse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;knowledge_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REVERSE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc_name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 查询改写：搜 &amp;#34;报告&amp;#34; → 反转为 &amp;#34;告报&amp;#34; → 前缀匹配
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;knowledge_document&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;REVERSE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REVERSE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;update_time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;实测结果&lt;/strong&gt;：P99 从 3200ms 降到 180ms，提升约 17 倍。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;局限性&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只解决了&amp;quot;包含&amp;quot;查询中&amp;quot;后缀匹配&amp;quot;的部分场景，对于中间位置的子串匹配效果有限&lt;/li&gt;
&lt;li&gt;每次查询需要对关键词做 &lt;code&gt;REVERSE()&lt;/code&gt; 转换，应用层需要适配&lt;/li&gt;
&lt;li&gt;函数索引在达梦中的维护成本比普通索引高，写入性能有一定损耗&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="方案三维持-like--限定范围"&gt;&lt;a href="#%e6%96%b9%e6%a1%88%e4%b8%89%e7%bb%b4%e6%8c%81-like--%e9%99%90%e5%ae%9a%e8%8c%83%e5%9b%b4" class="header-anchor"&gt;&lt;/a&gt;方案三：维持 LIKE + 限定范围
&lt;/h2&gt;&lt;p&gt;如果不想改索引结构，还有一个折中方案：在 LIKE 基础上增加限定条件，减少扫描范围。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;knowledge_document&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;doc_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;update_time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MONTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;update_time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;通过时间范围 + 分页限制，把扫描行数从 50 万压缩到几万。&lt;strong&gt;实测 P99 约 800ms&lt;/strong&gt;，能接受但不理想。&lt;/p&gt;
&lt;h2 id="三种方案对比"&gt;&lt;a href="#%e4%b8%89%e7%a7%8d%e6%96%b9%e6%a1%88%e5%af%b9%e6%af%94" class="header-anchor"&gt;&lt;/a&gt;三种方案对比
&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th style="text-align: center"&gt;全文索引 (CTXCAT)&lt;/th&gt;
&lt;th style="text-align: center"&gt;反向函数索引&lt;/th&gt;
&lt;th style="text-align: center"&gt;LIKE + 限定范围&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;查询性能 (P99)&lt;/td&gt;
&lt;td style="text-align: center"&gt;15ms&lt;/td&gt;
&lt;td style="text-align: center"&gt;180ms&lt;/td&gt;
&lt;td style="text-align: center"&gt;800ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;改造成本&lt;/td&gt;
&lt;td style="text-align: center"&gt;中（需改 SQL + 建索引 + 同步机制）&lt;/td&gt;
&lt;td style="text-align: center"&gt;低（加索引 + 改 SQL）&lt;/td&gt;
&lt;td style="text-align: center"&gt;极低（只改 SQL）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;存储开销&lt;/td&gt;
&lt;td style="text-align: center"&gt;高（+30%~50%）&lt;/td&gt;
&lt;td style="text-align: center"&gt;中（一列索引）&lt;/td&gt;
&lt;td style="text-align: center"&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;写入影响&lt;/td&gt;
&lt;td style="text-align: center"&gt;中（需异步同步）&lt;/td&gt;
&lt;td style="text-align: center"&gt;低（函数索引维护）&lt;/td&gt;
&lt;td style="text-align: center"&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;搜索质量&lt;/td&gt;
&lt;td style="text-align: center"&gt;高（支持分词、相关度排序）&lt;/td&gt;
&lt;td style="text-align: center"&gt;低（精确子串）&lt;/td&gt;
&lt;td style="text-align: center"&gt;低（精确子串）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适用数据量&lt;/td&gt;
&lt;td style="text-align: center"&gt;百万级以上&lt;/td&gt;
&lt;td style="text-align: center"&gt;十万~百万&lt;/td&gt;
&lt;td style="text-align: center"&gt;十万以内&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="迁移计划与回滚策略"&gt;&lt;a href="#%e8%bf%81%e7%a7%bb%e8%ae%a1%e5%88%92%e4%b8%8e%e5%9b%9e%e6%bb%9a%e7%ad%96%e7%95%a5" class="header-anchor"&gt;&lt;/a&gt;迁移计划与回滚策略
&lt;/h2&gt;&lt;p&gt;我们选择了全文索引方案，分三个阶段上线：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一阶段：并行部署（1 周）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在测试环境建全文索引，跑回归测试验证搜索结果一致性&lt;/li&gt;
&lt;li&gt;对比 LIKE 查询和 CONTAINS 查询的结果差异，重点验证中文分词效果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第二阶段：灰度切流（1 周）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过配置开关控制走新查询还是旧查询&lt;/li&gt;
&lt;li&gt;先在内部用户灰度 10%，观察搜索准确性和性能指标&lt;/li&gt;
&lt;li&gt;确认无误后逐步放量到 100%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第三阶段：清理旧逻辑（1 周后）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;移除 MyBatis 中的旧 LIKE 查询代码&lt;/li&gt;
&lt;li&gt;配置全文索引定时同步任务（每 5 分钟增量同步）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;回滚方案&lt;/strong&gt;：配置开关一键切回 LIKE 查询，全文索引可以异步删除，不影响线上服务。整个过程对用户透明。&lt;/p&gt;
&lt;h2 id="什么时候该上-elasticsearch"&gt;&lt;a href="#%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e8%af%a5%e4%b8%8a-elasticsearch" class="header-anchor"&gt;&lt;/a&gt;什么时候该上 Elasticsearch？
&lt;/h2&gt;&lt;p&gt;达梦的全文索引能解决 80% 的搜索优化需求，但以下场景建议直接引入 ES：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;需要复杂的相关度排序&lt;/strong&gt;（TF-IDF、BM25、自定义权重）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多字段联合搜索 + 高亮显示&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;搜索建议（Suggest）、拼写纠错、同义词扩展&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据量超过千万级，且对搜索延迟要求 &amp;lt; 50ms&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要跨多个异构数据源的统一搜索&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全文索引是数据库内置能力的天花板，ES 是专业搜索引擎的起点。如果你的搜索需求已经超出了&amp;quot;能搜到&amp;quot;的范畴，开始追求&amp;quot;搜得准、搜得快、搜得智能&amp;quot;，那就别在数据库里折腾了，老老实实搭一套 ES 集群。&lt;/p&gt;
&lt;p&gt;对于当前这个知识库场景，50 万文档 + 标题模糊搜索，达梦全文索引已经够用。先解决眼前的性能问题，等需求演进到复杂搜索再考虑架构升级，这才是务实的做法。&lt;/p&gt;</description></item></channel></rss>