概述
你用过 SQLite 存数据,也试过在应用层做 LIKE '%关键词%' 搜索?数据量小的时候还行,上万条就慢了,而且只支持前后缀模糊匹配,没法做真正的全文检索。
FTS5 是 SQLite 内置的全文搜索扩展,支持中文分词、相关性排序、短语查询、高亮标记。不需要装 Elasticsearch,不需要 Redis,一个扩展就能让 SQLite 变身搜索引擎。
这篇教程教你在 Linux VPS 或本地电脑上搭好 FTS5 开发环境,附带 Python 和 Node.js 的完整示例。
准备工作
你需要以下条件:
- 一台 Linux 机器(Ubuntu 22.04 / Debian 12 最佳)
- SQLite 3.9.0+(FTS5 从 3.9.0 开始内置,2015 年发布,基本都满足)
- Python 3.8+ 或 Node.js 18+
- 约 30MB 磁盘空间
先检查当前环境:
sqlite3 --version
python3 --version
node --version
SQLite 版本需要 3.9.0 以上。如果没装,运行:
sudo apt update && sudo apt install -y sqlite3 libsqlite3-dev python3 python3-pip nodejs npm
安装步骤
1. 创建 FTS5 测试数据库
进入项目目录,初始化数据库:
mkdir -p ~/fts5-demo && cd ~/fts5-demo
创建一个普通表加一个 FTS5 虚拟表的结构:
sqlite3 articles.db << 'EOF'
-- 普通表,存正文内容
CREATE TABLE articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- FTS5 虚拟表,只做全文检索
CREATE VIRTUAL TABLE articles_fts USING fts5(
title,
content,
content='articles',
content_rowid='id'
);
-- 触发器:普通表增删改时自动同步到 FTS5
CREATE TRIGGER articles_ai AFTER INSERT ON articles BEGIN
INSERT INTO articles_fts(rowid, title, content) VALUES (new.id, new.title, new.content);
END;
CREATE TRIGGER articles_ad AFTER DELETE ON articles BEGIN
INSERT INTO articles_fts(articles_fts, rowid, title, content) VALUES ('delete', old.id, old.title, old.content);
END;
CREATE TRIGGER articles_au AFTER UPDATE ON articles BEGIN
INSERT INTO articles_fts(articles_fts, rowid, title, content) VALUES ('delete', old.id, old.title, old.content);
INSERT INTO articles_fts(rowid, title, content) VALUES (new.id, new.title, new.content);
END;
EOF
验证表创建成功:
sqlite3 articles.db ".tables"
sqlite3 articles.db "SELECT name, type FROM sqlite_master WHERE type IN ('table','trigger')"
2. 插入测试数据
sqlite3 articles.db << 'EOF'
INSERT INTO articles (title, content) VALUES
('Python 异步编程指南', 'async 和 await 是 Python 异步编程的核心语法。配合 asyncio 库可以高效处理并发任务,避免回调地狱。'),
('Docker 容器化部署实战', 'Docker 让应用打包成镜像,在任何环境一致运行。配合 docker-compose 可以一键启动多服务架构。'),
('Kubernetes 集群管理入门', 'K8s 是当前主流的容器编排工具。理解 Pod、Service、Deployment 三个核心概念就能上手。'),
('Go 语言高性能网络编程', 'Go 的 goroutine 和 channel 让网络编程变得简单。net/http 标准库已经非常强大。'),
('Redis 缓存策略设计', '缓存穿透、缓存击穿、缓存雪崩是三个经典问题。加锁、布隆过滤器、随机过期时间分别对应不同方案。'),
('PostgreSQL 索引优化技巧', 'B-tree、GIN、GiST 三种索引适用于不同场景。全文搜索用 GIN,地理空间用 GiST。'),
('Linux 系统性能监控', 'top、htop、vmstat、iostat 是 Linux 性能分析的基础工具。看懂 CPU 和 IO 指标就能定位瓶颈。'),
('Nginx 反向代理配置', 'Nginx 做反向代理是最常见的用法。upstream、location、proxy_pass 三个指令够用大部分场景。'),
('Rust 所有权模型详解', 'Rust 的所有权系统编译期就能杜绝空指针和野指针。借用检查器是 Rust 最核心的特性。'),
('前端构建工具对比', 'Webpack、Vite、esbuild 各有优势。Vite 利用原生 ESM 实现秒级热更新,适合现代前端项目。');
EOF
3. 基础全文检索测试
sqlite3 articles.db << 'EOF'
-- 基础关键词搜索
SELECT title, snippet(articles_fts, 1, '<b>', '</b>', '…', 8) AS preview
FROM articles_fts
WHERE articles_fts MATCH '异步';
-- 多关键词 OR 搜索
SELECT title, snippet(articles_fts, 1, '<b>', '</b>', '…', 8)
FROM articles_fts
WHERE articles_fts MATCH '异步 OR 并发';
-- 短语精确匹配
SELECT title FROM articles_fts
WHERE articles_fts MATCH '"容器编排"';
-- 按相关性排序(近匹配优先)
SELECT title, score(articles_fts) AS relevance
FROM articles_fts
WHERE articles_fts MATCH 'Docker'
ORDER BY relevance DESC;
EOF
看到带 <b> 标签的高亮结果就说明 FTS5 工作正常。
4. 安装 Python 环境
Python 的 sqlite3 模块默认已经支持 FTS5(Python 3.9+ 内置):
cat > search.py << 'PYEOF'
import sqlite3
import sys
conn = sqlite3.connect('articles.db')
conn.row_factory = sqlite3.Row
cur = conn.cursor()
keyword = sys.argv[1] if len(sys.argv) > 1 else "Docker"
# 基础搜索
cur.execute("""
SELECT title, snippet(articles_fts, 1, '<b>', '</b>', '…', 8) AS preview
FROM articles_fts
WHERE articles_fts MATCH ?
ORDER BY rank
""", (keyword,))
print(f"\n搜索关键词: {keyword}\n")
for row in cur.fetchall():
print(f" [{row['title']}]")
print(f" {row['preview']}\n")
# 多关键词 OR
if ' ' in keyword:
terms = keyword.split()
query = " OR ".join(terms)
cur.execute("""
SELECT title, snippet(articles_fts, 1, '<b>', '</b>', '…', 8) AS preview
FROM articles_fts
WHERE articles_fts MATCH ?
ORDER BY rank
""", (query,))
print(f"\n多关键词搜索结果:\n")
for row in cur.fetchall():
print(f" [{row['title']}]")
print(f" {row['preview']}\n")
conn.close()
print("Python + FTS5 搜索完成!")
PYEOF
python3 search.py Docker
5. 安装 Node.js 环境
Node.js 用 sqlite3 npm 包:
mkdir -p ~/fts5-node-test && cd ~/fts5-node-test
npm init -y
npm install sqlite3
cat > search.js << 'JSEOF'
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('./articles.db');
const keyword = process.argv[2] || 'Docker';
db.all(
`SELECT title, snippet(articles_fts, 1, '<b>', '</b>', '…', 8) AS preview
FROM articles_fts
WHERE articles_fts MATCH ?
ORDER BY rank`,
[keyword],
(err, rows) => {
if (err) { console.error(err); return; }
console.log(`\n搜索关键词: ${keyword}\n`);
rows.forEach(row => {
console.log(` [${row.title}]`);
console.log(` ${row.preview}\n`);
});
db.close();
console.log('Node.js + FTS5 搜索完成!');
}
);
JSEOF
node search.js Docker
6. 高级用法:通配符和排除
sqlite3 articles.db << 'EOF'
-- 通配符搜索:匹配以 nginx 开头的所有词
SELECT title FROM articles_fts
WHERE articles_fts MATCH 'nginx*';
-- 排除搜索:搜索包含 Redis 但不包含缓存的
SELECT title FROM articles_fts
WHERE articles_fts MATCH 'Redis -缓存';
-- 嵌套短语搜索
SELECT title FROM articles_fts
WHERE articles_fts MATCH '"异步编程" AND Python';
EOF
验证
跑完整验证脚本:
cd ~/fts5-demo
# 1. 检查 FTS5 扩展已加载
sqlite3 articles.db "SELECT * FROM sqlite_master WHERE name='articles_fts';"
# 2. Python 搜索测试
python3 search.py "容器"
# 3. Node.js 搜索测试
cd ~/fts5-node-test && node search.js "Python"
# 4. 数据一致性检查
cd ~/fts5-demo
sqlite3 articles.db "SELECT COUNT(*) FROM articles; SELECT COUNT(*) FROM articles_fts;"
两个 COUNT 结果应该一致,说明普通表和 FTS5 表数据同步正常。
常见问题
Q: 中文搜索为什么不准?
A: FTS5 默认按空格分词,中文没有空格。需要用 tokenizer 指定分词方式。SQLite 内置了 unicode61 分词器,但中文效果一般。如果需要更好的中文分词,可以编译安装 jieba tokenizer 扩展,或者先用应用层分词再存入 FTS5。
Q: 数据量大了怎么办?
A: FTS5 单表支持上亿行数据。如果查询变慢,先跑 ANALYZE 更新统计信息:
sqlite3 articles.db "ANALYZE articles_fts;"
Q: 怎么重置 FTS5 索引?
A: FTS5 支持增量优化,也可以手动触发:
sqlite3 articles.db "OPTIMIZE articles_fts;"
Q: FTS5 和普通 LIKE 搜索性能差多少?
A: 100 万条数据测试,FTS5 毫秒级返回,LIKE 要几秒甚至超时。FTS5 用倒排索引,搜索复杂度是 O(1),LIKE 是 O(n)。
Q: 怎么迁移现有数据到 FTS5?
A: 先建好 FTS5 表结构,然后批量插入现有数据触发器会自动同步:
sqlite3 articles.db << 'EOF'
INSERT INTO articles (title, content) SELECT title, content FROM old_table;
EOF
FTS5 让 SQLite 从"只能存数据"变成了"能搜数据"。对于一个还在用 SQLite 的项目,加几行 SQL 就能获得接近 Elasticsearch 的搜索体验,性价比极高。
你的项目里有没有那种数据量不大、但需要全文检索的场景?