三者根本不是一类东西
选错通常是因为没分清形态:sqlite-vec 是 SQLite 的一个扩展,FAISS 是一个 C++ 库(不是服务),Chroma 是带 embedding 和元数据过滤的向量数据库。一个是"数据库里加个字段",一个是"自己造轮子的零件",一个是"开箱即用但偏原型的成品"。先把这张表看明白,再按规模和运维能力做决定。
| 方案 | 形态 | 适合规模 | 元数据过滤 | 持久化 |
|---|---|---|---|---|
| sqlite-vec | SQLite 扩展 | 单机 50 万以内 | 靠 SQL WHERE 自己拼 | 单个 .db 文件 |
| FAISS | 算法库 | 千万级 | 无,需自建映射 | write_index 手动存 |
| Chroma | 向量数据库 | 十万级原型 | 内置 where | PersistentClient |
| pgvector | Postgres 扩展 | 百万级 | SQL 全能力 | 随数据库 |
各自的最小可用代码
sqlite-vec 的价值是"一个文件 + 事务",向量和业务数据同库,备份、加字段、按条件过滤都用 SQL,嵌入式场景几乎零运维。建虚拟表时维度必须和 embedding 模型一致,插入用 float32 的字节串。
import sqlite3, sqlite_vec, numpy as np
db = sqlite3.connect("kb.db")
db.enable_load_extension(True)
sqlite_vec.load(db)
db.execute("CREATE VIRTUAL TABLE IF NOT EXISTS docs USING vec0(embedding float[1024])")
db.execute("CREATE TABLE IF NOT EXISTS meta (rowid INTEGER PRIMARY KEY, path TEXT, tag TEXT)")
v = np.array([0.1] * 1024, dtype=np.float32)
db.execute("INSERT INTO docs(rowid, embedding) VALUES (?, ?)", (1, v.tobytes()))
db.execute("INSERT INTO meta VALUES (?, ?, ?)", (1, "ai/ollama.md", "ai"))
db.commit()
q = np.array([0.1] * 1024, dtype=np.float32)
rows = db.execute(
"SELECT d.rowid, d.distance, m.path FROM docs d "
"JOIN meta m ON m.rowid = d.rowid "
"WHERE d.embedding MATCH ? AND m.tag = ? ORDER BY d.distance LIMIT 5",
(q.tobytes(), "ai"),
).fetchall()
FAISS 只负责算最近邻,返回的是下标和距离,id 到原文的映射、过滤、并发、落盘都得自己写。好处是索引类型可选、速度上限高。用余弦时务必先把向量归一化,否则 IndexFlatL2 的 L2 距离和余弦排序不等价;HNSW 的 M 和 efConstruction 决定内存与召回的平衡。
import faiss, numpy as np
vecs = np.random.rand(10000, 1024).astype("float32")
faiss.normalize_L2(vecs)
index = faiss.IndexHNSWFlat(1024, 32)
index.hnsw.efConstruction = 200
index.add(vecs)
q = np.random.rand(1, 1024).astype("float32")
faiss.normalize_L2(q)
D, I = index.search(q, 5) # I 是下标,自己去 meta 表里查对应文档
faiss.write_index(index, "docs.index")
Chroma 适合两天内要看到效果:自带 collection、元数据过滤和默认 embedding,代码量最少。坑也在这里,默认 all-MiniLM 是英文模型,中文不换 embedding_function 会明显退化。
import chromadb
client = chromadb.PersistentClient(path="./chroma")
col = client.get_or_create_collection("blog", metadata={"hnsw:space": "cosine"})
col.add(ids=["1"], documents=["Ollama 用 num_ctx 控制上下文"],
embeddings=[[0.1] * 1024], metadatas=[{"tag": "ai"}])
print(col.query(query_embeddings=[[0.1] * 1024], n_results=5,
where={"tag": "ai"}))
按场景给结论
- 单机工具、要嵌进应用:sqlite-vec。一个 .db 文件跟着 SQLite 事务走,备份就是拷文件。
- 原型验证、要自带过滤:Chroma,但别直接上生产核心链路,数据量和并发上去后召回和内存都不理想。
- 百万级以上、要极致召回:FAISS 的 IVF 或 HNSW;如果已经在用 Postgres,直接 pgvector 的 HNSW 索引,省一整套组件。
踩坑:维度写错,插入时有的库直接报错、有的悄悄存进去导致结果全乱;批量插入不包事务,sqlite-vec 会慢到怀疑人生;换 embedding 模型后维度和分布都变了,必须整库重建,新旧向量绝不能混查。