01向量数据库的作用
传统数据库用「关键词匹配」找东西:你搜「苹果」,它找包含「苹果」二字的行。 但人类提问往往语义相近、字面不同:
| 你的问题 | 关键词检索(FTS) | 向量检索 |
|---|---|---|
| 「怎么让程序开机自启」 | 需要文档里恰好有「开机自启」 | 能匹配到「systemd 服务管理」「守护进程」 |
| 「数据库模糊搜索哪个最像」 | 匹配「模糊」「搜索」 | 能匹配到「语义搜索」「相似度查询」 |
| 「上个月客户投诉最多的工单」 | 几乎无能为力 | 聚类后按主题聚合,可行 |
向量数据库的典型用途
- RAG(检索增强生成):把知识库切成块、向量化,提问时先召回最相关的几块,再让大模型基于它们回答。这是当前最主流的用途。
- 语义搜索:搜索不再依赖字面关键词。
- 去重 / 相似检测:论文查重、商品相似推荐、日志聚类。
- 推荐系统:用户向量与物品向量做近邻。
什么时候用 sqlite-vec 就够了
✓ 适合 sqlite-vec
- 数据量百万级以内
- 不想多维护一个数据库服务
- 业务表和向量要频繁 JOIN
- 单机应用、工具、原型验证
✗ 需要专用向量库
- 千万级以上,需要 GPU 加速检索
- 多节点分布式、高并发在线服务
- 需要 HNSW/IVF 等高级索引调参
- 已有 Milvus / Qdrant / pgvector 集群
load_extension 之后,
SQLite 就多了一个 vec0 虚拟表模块。你依然用 SQL 操作它,
依然可以用 JOIN 把向量结果和业务表拼起来——这是它相比
独立向量数据库最大的优势。
02极简示例:零依赖生成向量 + 查询
完整跑通向量检索需要两件事:把文本变成向量(embedding),把向量存起来做近邻查询。 第一件事通常要下载模型,为了让你第一次跑通时零外网依赖, 先用哈希函数伪造一个「假 embedding」——它不具备语义,但能让你看清整个流程。
import hashlib, math, sqlite3
import sqlite_vec
DIM = 8
def fake_embed(text: str) -> list[float]:
"""把文本散列成 8 维单位向量。仅用于演示流程,不具备语义能力。"""
h = hashlib.sha256(text.encode("utf-8")).digest()
vec = [(h[i] - 128) / 128.0 for i in range(DIM)]
norm = math.sqrt(sum(x * x for x in vec)) or 1.0
return [x / norm for x in vec]
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
sqlite_vec.load(db)
db.enable_load_extension(False)
print("sqlite:", sqlite3.sqlite_version,
"| vec:", db.execute("select vec_version()").fetchone()[0])
# 1) 建虚拟表:vec0 是 sqlite-vec 提供的虚拟表模块
db.execute(f"create virtual table docs using vec0(embedding float[{DIM}])")
# 2) 原文单独存普通表,虚拟表只管向量(rowid 做关联)
db.execute("create table docs_text(rowid integer primary key, body text)")
corpus = [
"nginx 反向代理配置示例",
"如何用 systemd 管理后台服务",
"SQLite 全文检索 FTS5 用法",
"向量数据库的近似最近邻搜索",
"Python 虚拟环境 venv 使用指南",
]
for i, text in enumerate(corpus, start=1):
db.execute("insert into docs(rowid, embedding) values (?, ?)",
(i, sqlite_vec.serialize_float32(fake_embed(text))))
db.execute("insert into docs_text(rowid, body) values (?, ?)", (i, text))
db.commit()
# 3) KNN 查询:match + limit 是 vec0 的固定写法,distance 是内置列
q = "SQLite 检索"
rows = db.execute("""
select docs.rowid, docs_text.body, docs.distance
from docs
join docs_text on docs_text.rowid = docs.rowid
where docs.embedding match ?
and k = 3
order by docs.distance
""", (sqlite_vec.serialize_float32(fake_embed(q)),)).fetchall()
print(f'查询「{q}」的最近 3 条:')
for rowid, body, dist in rows:
print(f" #{rowid} distance={dist:.4f} {body}")
# 4) 自检:同一文本反查 distance 必为 0,用来确认 embedding 链路接对了
same = db.execute(
"select distance from docs where embedding match ? and k = 1",
(sqlite_vec.serialize_float32(fake_embed(corpus[2])),),
).fetchone()
print(f"\n自检:用第 3 条原文反查,distance = {same[0]:.6f}(应为 0)")
sqlite: 3.45.1 | vec: v0.1.9
已写入 5 条向量
查询「SQLite 检索」的最近 3 条:
#4 distance=0.9491 向量数据库的近似最近邻搜索
#2 distance=1.3392 如何用 systemd 管理后台服务
#5 distance=1.3560 Python 虚拟环境 venv 使用指南
自检:用第 3 条原文反查,distance = 0.000000(应为 0)
03换真模型:同一份代码,效果天差地别
把 fake_embed 换成开源中文模型 BAAI/bge-small-zh-v1.5
(512 维),其余 sqlite-vec 代码一行不用改——
向量维度变了、embedding 有语义了,仅此而已:
from sentence_transformers import SentenceTransformer
MODEL = "BAAI/bge-small-zh-v1.5"
model = SentenceTransformer(MODEL)
DIM = model.get_embedding_dimension() # 512
print(f"模型 {MODEL} | 维度 {DIM}")
def embed(texts):
# normalize_embeddings=True 后,L2 距离与余弦相似度单调等价
return model.encode(texts, normalize_embeddings=True).tolist()
# …… 其余部分与 demo.py 完全相同:建 vec0 表、插入、KNN 查询 ……
模型 BAAI/bge-small-zh-v1.5 | 维度 512
查询「SQLite 检索」:
distance=0.6498 SQLite 全文检索 FTS5 用法 ← 这次对了
distance=0.9797 向量数据库的近似最近邻搜索
distance=1.0504 如何用 systemd 管理后台服务
查询「怎么让程序开机自启」:
distance=1.0448 如何用 systemd 管理后台服务 ← 语义匹配成功
distance=1.1068 nginx 反向代理配置示例
distance=1.1625 Python 虚拟环境 venv 使用指南
查询「把域名请求转发到后端」:
distance=0.9753 如何用 systemd 管理后台服务 ← 这个错了!见下文
04sqlite-vec 用法详解
4.1 vec0 虚拟表
-- 固定语法:embedding 列 + 向量维度
create virtual table docs using vec0(embedding float[512]);
-- 插入:向量必须先用 serialize_float32 序列化成 BLOB
insert into docs(rowid, embedding) values (1, ?);
-- Python 侧:sqlite_vec.serialize_float32(vec)
-- KNN 查询:match 传入查询向量,k 是返回条数
select rowid, distance from docs
where embedding match ?
and k = 10
order by distance;
| 要点 | 说明 |
|---|---|
distance 是内置列 | 查询结果自动带出,默认 L2 距离(越小越相似) |
| 维度 | 建表时定死,插入维度不符会报错;换模型维度变了要重建表 |
| float 类型 | float[N] 用 float32;另有 int8(量化到 1/4 体积) |
| rowid | 虚拟表没有普通主键,用 rowid 与业务表 JOIN |
| 原文不存向量表 | 原文放普通表,虚拟表只放向量 + rowid,这是推荐架构 |
4.2 与业务表 JOIN:最常用的组合拳
select docs_text.body, docs.distance
from docs
join docs_text on docs_text.rowid = docs.rowid
where docs.embedding match ?
and k = 3
order by docs.distance;
4.3 向量检索 + 关键词过滤:混合检索
向量检索擅长语义,FTS5 擅长精确词。很多场景两者结合效果最好——
比如先按分类字段过滤,再在子集里做向量近邻。vec0 支持把约束条件
直接写在 where 里(如 where category_id = 3 and embedding match ?)。
vec_distance_l2 函数支持这种「不建虚拟表」的用法,
对原型验证更简单。
05常用向量模型实测对比(中文场景)
以下数据均为本机真实跑出来的(同一语料、同一查询、top-1 命中率)。 语料为 5 条中文技术文档(nginx / systemd / SQLite / 向量库 / venv):
| 模型 | 维度 | 体积 | top-1 命中 | 说明 |
|---|---|---|---|---|
BAAI/bge-small-zh-v1.5 | 512 | ~95MB | 2/3 | 国产开源,中文友好,CPU 可跑,入门首选 |
BAAI/bge-base-zh-v1.5 | 768 | ~400MB | 2/3 | 更大更准,但本例中未超过 small(瓶颈在语料) |
paraphrase-multilingual-MiniLM-L12-v2 | 384 | ~470MB | — | 多语言通用,英文场景更稳 |
BAAI/bge-m3 | 1024 | ~2.2GB | — | 支持 100+ 语言,效果最强,需要更好硬件 |
| OpenAI / 火山方舟等 API 向量模型 | 视服务而定 | 0(远程) | — | 省本地资源,但按量付费、有网络依赖 |
怎么选
- 本地原型 / 个人工具:bge-small-zh-v1.5,CPU 跑,几百毫秒一批。
- 中文为主的生产:bge-base-zh-v1.5 起步,批量预计算可以接受更大模型。
- 多语言 / 高精度:bge-m3 或 API 向量模型。
- 有 GPU:直接上大模型,维度越高信息越密,但存储和计算成本也涨。
06Ubuntu 部署 sqlite-vec
6.1 三步装好(本教程在 Ubuntu 24.04 + Python 3.12 验证)
# 1) 建虚拟环境(强烈建议,避免污染系统 Python)
python3 -m venv .venv
. .venv/bin/activate
# 2) 安装 sqlite-vec(Python 绑定,自带编译好的扩展)
pip install sqlite-vec
# 验证:python -c "import sqlite_vec; print('ok')"
# 3) 需要真模型时再装 sentence-transformers(注意下面的坑!)
pip install sentence-transformers
pip install sentence-transformers 会顺带安装 PyTorch,
默认源会拉 几个 GB 的 CUDA 工具包(我实测在 1.3MB/s 的网速下
拖了 526MB + 366MB + 170MB 还没完)。纯 CPU 场景这样装纯属浪费。
正确做法是先用 CPU 版 torch 挡住:
# 关键:先装 CPU 版 torch,再装 sentence-transformers
# 这样它不会再拖 CUDA 包,几十秒搞定
pip install --index-url https://download.pytorch.org/whl/cpu torch
pip install sentence-transformers
# 验证:python -c "import torch; print(torch.__version__)" # 2.x.x+cpu
6.2 加载扩展的两种姿势
# 姿势 A:Python 里加载(推荐,教程全程用这个)
import sqlite3, sqlite_vec
db = sqlite3.connect("app.db")
db.enable_load_extension(True)
sqlite_vec.load(db)
db.enable_load_extension(False)
# 姿势 B:SQLite CLI 里加载(先找到 .so 路径)
# python -c "import sqlite_vec; print(sqlite_vec.loadable_path())"
# 然后:
# .load /path/to/vec0.so
# select vec_version();
6.3 生产环境注意事项
| 事项 | 建议 |
|---|---|
| 备份 | 向量库就是个 SQLite 文件,sqlite3 app.db ".backup bk.db" 即可热备;或直接 cp 文件(先开 WAL) |
| WAL 模式 | 高频写入开 PRAGMA journal_mode=WAL,读写不互锁 |
| embedding 离线化 | 模型首次运行会下载权重,生产环境提前下载好,或设 HF_HUB_OFFLINE=1 |
| 批量预计算 | 入库时批量 encode(一次喂 100 条比循环 100 次快一个数量级),存好向量再入库 |
| 维度变更 | 模型版本升级 = 重建表 + 重新向量化,提前规划迁移脚本 |