花拾录
← 返回知识库

SQLite 全文检索搜两个中文字返回空:trigram 分词器的长度限制

数据库导入2026/09/220 阅读0 评论

你给站内搜索加上了全文检索:建表、灌数据、写查询,一切顺利。可当你在搜索框里敲下最常见的两字中文词——"列表""设置""评论"——结果一条都没有。换成三个字、四个字,又正常了。你会怀疑是数据没进去,或者中文分词出了问题,但重建索引也没用。

现象

索引建表成功,数据也确实灌进去了,偏偏短词搜不到:

SELECT * FROM docs WHERE docs MATCH '列表';
-- 返回 0 行

SELECT * FROM docs WHERE docs MATCH '列表工具';
-- 正常返回若干行

规律很清晰:查询词不足 3 个字符时返回空,达到或超过 3 个字符就正常。 而中文里两字词恰恰是最常见的搜索长度——"登录""缓存""权限",全是两字。于是搜索功能在用户眼里几乎是坏的。

根因

根因在 trigram 分词器的切分规则,而不是中文分词本身。

SQLite 的 FTS5 提供一个 trigram 分词器,它是为子串匹配设计的:把整段文本按"每 3 个字符一个片段、每次滑动 1 个字符"切成一组三联片段,查询时同样切成三联片段去比对。

举个例子,文本"Claude 踩坑"会被切成 Cla、lau、aud、ude……这样一叠三联片段。查询"踩坑"时,分词器要把查询词也切成三联片段——可"踩坑"只有 2 个字符,凑不满一个 3 字片段,于是切出空集。查询词切不出任何片段,自然匹配不到任何东西。

这就是"短词完全失效"的机制:trigram 有一个硬性的长度下限——3 个字符。 低于这个长度,它在分词阶段就已经"无货可发",检索无从谈起。中文两字词正好全部落在下限之下,而它又是最高频的查询长度,所以问题显得特别严重。

要区分的是:这不是"中文分词没做对"。trigram 本来就按固定长度切,跟语言无关。它适合做子串模糊匹配,但它对查询长度有硬约束。

解决

思路:对短查询做回退,用 LIKE 子串匹配兜底。

在查询入口判断词长,分两条路径:

-- 查询词长度 >= 3:走全文索引,快
SELECT * FROM docs WHERE docs MATCH :kw;

-- 查询词长度 < 3:回退到子串匹配
SELECT * FROM docs WHERE content LIKE '%' || :kw || '%';

应用层的判断逻辑大致是:

if (len(kw) >= 3)  -> 用 MATCH 走 FTS 索引
else               -> 用 LIKE '%kw%' 全表扫描

注意几个配套细节:

  • 回退路径会变慢。 LIKE '%词%' 用不到索引,是全表扫描。两字词查询量大时,性能会明显下降。可以给它加一层缓存,或者接受"短词查询慢一点"这个取舍。
  • LIKE 的通配符要转义。 用户输入的词里若含 % 或 _,需要转义,否则会被当成通配符。用 LIKE ... ESCAPE '\' 并预处理输入。
  • 索引重建时注意。改用别的分词方案(比如给中文引入第三方分词)属于更大的改动,权衡成本后再动;多数场景下"短词回退 LIKE"就是性价比最高的解。

改造后做一个覆盖测试:分别用 2 字、3 字、4 字词各搜一遍,确认短词走回退路径、长词走索引路径,都能返回结果。

延伸与预防

这条坑的通用教训是:索引的适用长度,必须和你的查询长度分布对得上。

在设计检索方案前,先看一眼真实查询的长度分布——中文场景里两字词占大头,这是必须纳入设计的前提。凡是"分词器、n-gram、前缀匹配"这类按长度切分的机制,都要先问一句:我的最短查询是多少字符,它落在分词器的下限之上吗?

更普遍地说,任何索引都有它的"适用边界",边界之内是加速,边界之外往往是静默失效——不报错,只是搜不到。上线前用真实分布的数据去压一遍,比读文档更能发现问题。

评论(0)

  • 还没有评论,来抢沙发~

相关文章