上一章讲了基础知识,文本可以转成向量,可以根据query进行召回。那在现实场景中怎么存储,怎么搜索呢?
向量数据库对比
下面是一些常用的向量数据库,我VikingDB用的多一些。
| 名称 | 类型 | 部署方式 | 规模上限 | 核心优势 | 核心劣势 | 适用场景 |
|---|---|---|---|---|---|---|
| Chroma | 轻量开源 | 本地 / 嵌入 | 百万级 | 5 行代码跑通,Python 友好,LangChain 标配 | 单机,功能少,性能一般 | 原型验证、小项目、个人开发 |
| Qdrant | 开源 | 自部署 / SaaS | 亿级 | Rust 写的,单机性能极强,混合检索好 | 分布式不如 Milvus 成熟,国内社区小 | 中大型项目,追求单机性能 |
| pgvector | PG 扩展 | 自部署 | 千万级 | 不用学新库,SQL 直接查,事务 / 权限全有 | 性能不如专用向量库,规模受 PG 限制 | 已有 PG 栈,小中型项目 |
| Weaviate | 开源 | 自部署 / SaaS | 亿级 | 知识图谱 + 向量一体化,GraphQL 灵活 | 纯向量性能一般,国内用户少 | 知识图谱 + RAG、复杂语义查询 |
| Milvus | 开源 | 自部署 | 十亿级 + | 分布式最强,功能最全,国内社区活跃 | 部署复杂,组件多,运维成本高 | 超大规模、企业级生产环境 |
| Pinecone | 闭源 SaaS | 全托管 | 十亿级 + | 零运维,自动扩容,稳定有 SLA | 贵,闭源,数据在海外有合规风险 | 不想运维、快速上线、海外项目 |
| VikingDB | 云服务 | 火山引擎 SaaS | 万亿级 | 字节自研,千亿级验证,混合检索强,内置豆包 embedding | 绑定火山生态,开源版能力有限 | 国内企业、大规模场景、火山生态 |
| Elasticsearch | 搜索引擎 | 自部署 / 云 | 亿级 | 全文 + 向量混合检索最强,已有 ES 栈零成本接入 | 8.0 才支持 ANN,纯向量性能一般 | 已有 ES、需要全文 + 语义混合检索 |
| ClickHouse | OLAP 数据库 | 自部署 | 亿级 | SQL 分析能力极强,向量化引擎快,和数仓集成好 | 向量功能新(25.8 才 GA),生态不如专用库 | 已有 CH 数仓、需要分析 + 向量检索 |
| Redis Vector | 缓存 / 数据库 | 自部署 / 云 | 千万级 | 速度极快,已有 Redis 栈直接用 | 规模有限,功能简单 | 缓存 + 轻量向量、已有 Redis 栈 |
| SQLite + sqlite-vec | 嵌入式扩展 | 十万级 | 零依赖、超轻量、全平台、WASM 支持 | 无 ANN 索引,规模大了慢 | 移动端、嵌入式、本地小工具、浏览器端 |
SQLite
安装
OpenClaw 选择 SQLite 作为存储引擎,那就试试怎么用吧。配合两个关键扩展
sqlite-vec:向量搜索扩展,支持余弦相似度计算,实现高效的 ANN(近似最近邻)检索。FTS5:SQLite 内置的全文搜索引擎,支持 BM25 排序算法。
OpenClaw用SQLite主要是为了Local First。
1 | 是否安装了,我mac上自带了 |
BM25排序算法
BM25(Best Match 25)是全文搜索里最经典的相关性排序算法,用来算”查询词和文档的相关程度”,分数越低越相关(SQLite FTS5 里就是这样)。
📐 BM25 公式
核心公式长这样:

看着复杂,拆开来看就三部分:
🔍 拆解成大白话
第一部分:IDF(逆文档频率)

意思:越稀有的词,权重越高。
- “的”、”是”这种到处都有的词 → IDF 低,几乎不加分
- “量子力学”这种很少见的词 → IDF 高,匹配上了加很多分
第二部分:词频(TF)的饱和曲线

意思:词出现次数越多越相关,但有上限,不会无限涨。
- 出现 1 次 → 加很多分
- 出现 5 次 → 比 1 次分高,但不是 5 倍
- 出现 100 次 → 和出现 20 次差不多,饱和了
对比 TF-IDF:TF-IDF 是线性的,出现 100 次就是 100 倍分,不合理。BM25 是曲线,越往上越平。
第三部分:文档长度归一化

意思:长文档要惩罚。
- 文档越长,词出现的概率天然就高,不公平
- 同样出现 5 次”苹果”,在 100 字的短文里比在 10000 字的长文里更有意义
- 文档越长,分母越大,整体分数越低(惩罚)
⚙️ 两个关键参数
| 参数 | 常用值 | 作用 |
|---|---|---|
| k1 | 1.2 ~ 2.0 | 控制词频饱和速度。k1 越小,越快饱和 |
| b | 0.75 | 控制文档长度惩罚强度。b=0 不惩罚,b=1 全惩罚 |
SQLite FTS5 默认值:k1=1.2, b=0.75,是行业标准值,一般不用改。
📊 举个例子
假设查询是 "苹果",有三个文档:
| 文档 | 长度 | “苹果”出现次数 | BM25 分数 | 排名 |
|---|---|---|---|---|
| A(短文) | 100字 | 3次 | 1.2 | 🥇 最相关 |
| B(长文) | 5000字 | 10次 | 2.5 | 🥈 |
| C(中等) | 500字 | 1次 | 3.8 | 🥉 |
注意:SQLite FTS5 的 bm25() 返回的是距离,越小越相关。上面的分数是反过来的概念。
为什么 A 排第一?
- 虽然出现次数少,但文档短,”浓度”高
- BM25 认为短文档里出现 3 次,比长文档里出现 10 次更能说明相关
🆚 BM25 vs TF-IDF
| 维度 | TF-IDF | BM25 |
|---|---|---|
| 词频关系 | 线性(出现10次=10倍分) | 饱和曲线(有上限) |
| 文档长度 | 不考虑 / 简单归一化 | 专门的长度归一化公式 |
| 参数 | 无 | k1、b 可调 |
| 效果 | 基础可用 | 更精准,工业界标准 |
一句话:BM25 是 TF-IDF 的进化版,考虑了词频饱和和文档长度,排序更合理。
💡 在 SQLite FTS5 里怎么用
1 | -- 默认用法,按 bm25 排序(分数越小越相关) |
🎯 总结
BM25 的核心思想就三句话:
- 稀有词权重高(IDF)
- 词频有上限(饱和曲线)
- 长文档要惩罚(长度归一化)
它是 20 多年前发明的算法,但至今仍是全文搜索的工业标准,Elasticsearch、SQLite FTS5、Lucene 等默认都是 BM25。
实战
1 | import sqlite3 |
执行结果
1 | (sqlite) ➜ sqlite python3 test.py |
命令行执行
1 | 第一步:终端里打开数据库,如果不存在会自动创建 |