SQLite的FTS5(Full-Text Search version 5)是一个功能相当完善的全文搜索扩展,它以虚拟表的形式存在,能够在不引入任何外部依赖的情况下,为应用提供倒排索引能力。对于桌面软件、移动端App、小型网站这类数据量在百万级以内的场景,FTS5的性能完全够用,而且维护成本几乎为零。这篇文章从建表开始,一步步讲清FTS5的用法,并重点讨论中文场景下的分词配置问题。

FTS5是什么,为什么值得用
FTS5是SQLite 3.9.0之后内置的全文搜索模块,它通过维护一张倒排索引表,把每个分词出现的行号和位置记录下来,使得查询时不必逐行扫描,速度远快于LIKE '%关键词%'这种模糊匹配。LIKE查询无法利用普通索引,数据量一上来就会退化成全表扫描,而FTS5的查询复杂度只与命中行数相关。
FTS5相比它的前代FTS4有几个明显优势:支持更灵活的排名函数bm25(),查询语法更接近主流搜索引擎(支持AND、OR、NOT、NEAR等操作符),索引体积更小,并且可以按列建立索引权重。它的典型应用场景包括聊天记录搜索、笔记软件、邮件归档、日志检索等本地化数据查询。
需要注意的是,FTS5默认随官方发行版一起编译,但某些精简编译版(比如部分Linux发行版的系统自带版本)可能没有启用。可以通过执行SELECT fts5(?);,如果返回错误则说明当前环境不支持,需要重新编译SQLite并打开SQLITE_ENABLE_FTS5选项。
创建FTS5虚拟表与基本查询
FTS5表是一种虚拟表,创建语法很简单。下面是一个最基础的例子:
-- 创建一张带全文索引的虚拟表
CREATE VIRTUAL TABLE notes USING fts5(title, body);
-- 插入数据时会自动分词并建立索引
INSERT INTO notes(title, body) VALUES
('SQLite教程', 'SQLite是一个轻量的嵌入式数据库'),
('全文搜索入门', 'FTS5提供高效的倒排索引能力');
-- MATCH查询:默认多个词之间是 AND 关系
SELECT * FROM notes WHERE notes MATCH 'SQLite';
SELECT * FROM notes WHERE notes MATCH '搜索 AND 入门';
-- 使用bm25函数按相关度排序,值越小越相关
SELECT title, bm25(notes) FROM notes
WHERE notes MATCH 'SQLite'
ORDER BY bm25(notes);
几个细节值得注意。第一,MATCH查询必须写在WHERE 表名 MATCH '关键词'的形式,不能直接对某一行用=比较。第二,默认所有列都会被索引,如果某些列只需要存储不需要检索,可以用UNINDEXED标记,例如CREATE VIRTUAL TABLE notes USING fts5(title, body, created_at UNINDEXED);,这样能减小索引体积。
查询语法方面,FTS5支持的操作符相当丰富:AND、OR、NOT是基本逻辑;"精确短语"用双引号包裹可以做短语匹配;NEAR(a b, 5)表示两个词在5个词以内相邻出现;col:词可以限定只在某一列中搜索,例如title:SQLite只匹配标题列。另外,rank是一个特殊的隐藏列,等价于bm25相关度,可以直接ORDER BY rank。
中文分词问题的解决方案
FTS5最大的短板在于内置分词器对中文的支持。默认的unicode61分词器按标点和空白切分,对英文效果好,但遇到连续中文会整句当成一个token,导致中文搜索基本不可用。解决思路主要有三种。
第一种是使用trigram分词器(SQLite 3.34.0起内置),它把文本切成三个字符的滑动窗口片段,天然支持中文,并且还能配合LIKE实现子串匹配:
-- 使用trigram分词器建表 CREATE VIRTUAL TABLE articles USING fts5( content, tokenize = 'trigram' ); -- 可以直接匹配中文子串 SELECT * FROM articles WHERE articles MATCH '数据库原理';
trigram的缺点是索引膨胀比较明显,而且搜索词少于三个字符时无法命中。第二种方案是在应用层先把中文文本分好词(比如用jieba),再用空格拼接后写入FTS5,查询时同样先分词再拼接成查询串。这种方案索引小、可控性强,但需要维护分词逻辑的一致性,写入端和查询端必须使用同一套分词器,否则会出现搜不到的情况。
第三种是编译自定义分词器,比如集成Simple或基于ICU的分词实现,直接在C层完成分词。这种方案性能最好,但需要自己维护SQLite的编译产物,部署门槛较高。对于大多数应用,如果搜索词长度可以保证,trigram是最省事的选择;如果对索引体积敏感,应用层分词则更均衡。
数据同步与性能优化建议
实际项目中,通常业务数据存在普通表中,FTS5表只作为搜索索引。这时推荐使用外部内容表(external content table)模式,让FTS5不重复存储数据,只维护索引:
-- content选项指向原始表,不重复存数据
CREATE VIRTUAL TABLE notes_fts USING fts5(
title, body,
content='notes', -- 数据来源表
content_rowid='id' -- 对应原始表的主键
);
-- 用触发器保持索引同步
CREATE TRIGGER notes_ai AFTER INSERT ON notes BEGIN
INSERT INTO notes_fts(rowid, title, body)
VALUES (new.id, new.title, new.body);
END;
CREATE TRIGGER notes_ad AFTER DELETE ON notes BEGIN
INSERT INTO notes_fts(notes_fts, rowid, title, body)
VALUES ('delete', old.id, old.title, old.body);
END;
CREATE TRIGGER notes_au AFTER UPDATE ON notes BEGIN
INSERT INTO notes_fts(notes_fts, rowid, title, body)
VALUES ('delete', old.id, old.title, old.body);
INSERT INTO notes_fts(rowid, title, body)
VALUES (new.id, new.title, new.body);
END;
这种模式能把存储开销压缩到最低,但要特别注意删除和更新必须通过特殊的'delete'命令写入,否则索引会产生脏数据。如果嫌触发器麻烦,也可以定期执行INSERT INTO notes_fts(notes_fts) VALUES('rebuild');全量重建索引,适合数据更新不频繁的离线场景。
性能方面还有几点经验:索引建立有代价,批量写入时可以先删触发器、写完数据后统一rebuild,比逐行维护索引快一个数量级;查询频繁的列可以配合bm25的列权重参数(如bm25(notes, 10.0, 1.0)表示title权重是body的十倍)来优化排序效果;数据库连接记得开启PRAGMA journal_mode=WAL;,读写并发场景下体验会好很多。掌握这些配置后,FTS5足以支撑绝大多数本地搜索需求,是一个性价比极高的技术选型。
SQLite FTS5全文搜索中文分词修改时间:2026-09-10 03:52:34