向量数据库:从 SQL 到语义搜索

理解 Embedding → HNSW → ChromaDB 的完整链路,用直觉而非公式

🔍 关键词搜索 vs 语义搜索 —— 直观对比

左边模拟传统数据库的字面匹配逻辑(MySQL LIKE),右边是向量数据库的语义匹配逻辑(ChromaDB)。 这两个查询词在文档中一个字都没出现,看看两边分别能搜到什么。

❌ 关键词搜索(字面匹配)

字符级匹配:查询词中的每个字是否出现在文档中
选择一个预设查询或输入自定义查询

✅ 向量语义搜索(语义理解)

基于 bert-base-chinese 真实 embedding 的余弦相似度排序
选择一个预设查询或输入自定义查询

🤔 MySQL 为什么搜不到"好吃的麻辣菜"?

-- 文档里写的是"麻辣",用户搜的是"好吃的",字面完全没有交集
SELECT * FROM documents WHERE content LIKE '%好吃的麻辣菜%';
-- 返回:0 行

-- 就算精确搜"麻辣",也只能匹配到恰好包含这两个字的文档
-- "香辣""酸辣""辛辣"——都不会被匹配到
核心问题:传统数据库只能做字面匹配,完全不理解"意思"。而向量数据库把"意思"变成了可以计算的数字。

🧬 一句话理解向量数据库

文档"麻婆豆腐"
Embedding模型
768维向量[0.12, -0.45,...]
HNSW 索引分层存储
查询"好吃的辣菜"
同一个Embedding 模型
查询向量
ANN 搜索找最近邻居
关键:写入和查询必须用同一个 Embedding 模型,否则向量空间不对齐,检索结果随机。

📐 HNSW 索引直觉

HNSW = Hierarchical Navigable Small World,分层的"导航图"。

类比:从北京去中关村某条小巷的咖啡店——

第 2 层(最稀疏):北京 → 海淀区 — 大步跳,快速定位
第 1 层(中等):海淀区 → 中关村街道 — 中步走
第 0 层(最密):中关村街道 → 咖啡店 — 小步精确

HNSW 对向量做同样的事——顶层只有几十个节点可以大步跨越,底层的密集节点保证精度。最终只访问了总向量的很小一部分 → O(log N)

🎯 ANN = 近似最近邻

不找"精确最近的那一个",找"足够近的那几个"。用精度换速度。

暴力搜索O(N) 时间100% 精确
ANN 搜索O(log N)~99% 精确
为什么近似就够了?语义搜索本来就不是精确的——搜"好吃的辣菜"返回麻婆豆腐还是水煮鱼,都是对的。这和 SQL WHERE id=123 必须精确到一行不同。

🏗️ ChromaDB 层级结构

Client客户端连接
Collection类似 SQL 的"表"
Documents文本 + 向量 + 元数据
Collection = SQL Table 的类比非常实用。每个 Collection 绑定一个 Embedding 函数,add() 时自动把文本转成向量,query() 时自动把查询文本转成向量然后搜 HNSW 索引。

🧭 真实语义空间(bert-base-chinese + PCA 降维到 2D)

所有文档和查询都用 bert-base-chinese 生成了真实的 768 维向量,然后 PCA 降到 2 维可视化。 可以清晰看到同类文档自然聚集,查询向量靠近相关文档簇

科技类 美食类 运动类 查询向量

📊 六种数据库/引擎对比总览

MySQLPostgreSQL
+ pgvector
ElasticsearchChromaDBFAISSMilvus
类型 关系型关系型搜索引擎 向量数据库向量检索库 向量数据库
向量支持 ✗ 不支持✓ pgvector✓ 8.0+ ✓ 原生✓ 核心✓ 原生
部署复杂度 极低(纯库)
数据持久化 ✗ 需手动
混合查询N/A
分布式支持支持原生 ✗ 单机✗ 单机原生
适用量级N/A百万千万 十万~百万亿级十亿级
一句话 关系型之王
但没向量
一库两用
百万以内首选
全文+语义
混合搜索王者
向量DB界的SQLite
零配置
极致速度
但它不是数据库
向量DB界的PG
生产就绪

🗺️ 选型决策树

要选向量数据库?
├─ 只是学习/Demo/原型?
│   └─ → ChromaDB(pip install 即可)
├─ 已有 PostgreSQL?
│   └─ 数据量 < 百万 → pgvector(零迁移成本)
├─ 需要全文 + 语义混合搜索?
│   └─ → Elasticsearch
├─ 生产环境、大规模、低延迟?
│   └─ → Milvus(分布式、十亿级)
├─ 离线分析、学术研究?
│   └─ → FAISS(需自建持久化)
└─ 纯结构化查询、不需要语义?
    └─ → MySQL / PostgreSQL(传统的就够了)

💻 Demo 代码核心片段讲解

1. 自定义 Embedding 函数(中文适配)

class ChineseBertEmbedding(EmbeddingFunction):
    """用 bert-base-chinese 生成句向量"""
    def __call__(self, texts):
        for text in texts:
            inputs = self.tokenizer(text, return_tensors="pt")
            with torch.no_grad():
                outputs = self.model(**inputs)
            # Mean Pooling:所有 token 向量取平均 → 句向量
            pooled = mean_pooling(outputs, inputs["attention_mask"])
            # L2 归一化:让所有向量长度=1,余弦相似度等价于点积
            pooled = F.normalize(pooled, p=2, dim=1)
            embeddings.append(pooled[0].tolist())
        return embeddings
为什么不用 ChromaDB 内置的 DefaultEmbeddingFunction?因为内置的是 英文模型,对中文语义理解很差——搜"麻辣菜"可能返回篮球相关内容。

2. 创建 Collection 并导入文档

# 创建客户端(内存模式)
client = chromadb.Client()
collection = client.create_collection(
    name="knowledge_base",
    embedding_function=ChineseBertEmbedding(),
    metadata={"hnsw:space": "cosine"},  # 用余弦距离
)

# add() 自动调用 embedding_function 把 text → 向量 → HNSW 索引
collection.add(
    documents=["麻婆豆腐是一道经典的四川名菜..."],
    metadatas=[{"category": "food"}],
    ids=["doc_4"],
)

3. 语义查询 + 元数据过滤

# 纯语义搜索
results = collection.query(
    query_texts=["适合拍照的手机"],
    n_results=3,
)

# 混合查询:先过滤 category='tech',再在过滤结果中做向量检索
results = collection.query(
    query_texts=["和 AI 相关的技术"],
    n_results=3,
    where={"category": "tech"},
)

4. 关键词搜索对比(为什么不够用)

def simulate_keyword_search(query):
    # 按字符出现次数打分
    query_chars = set(query) - stop_chars
    for text in documents:
        score = sum(1 for c in query_chars if c in text)
    # → "辣的菜"搜不到"麻婆豆腐"

⚠️ 6 个常见坑

坑1:Embedding 模型不一致
导入文档用模型 A,查询时用模型 B → 向量空间错位,结果随机。
解法:在 Collection 创建时绑定 embedding 函数,一直复用。
坑2:文本分块不合理
500 页的书作为一条 document embed 进去 → 平均值淹没了细节。
解法:chunk 200-500 字/块,块之间 10-20% 重叠。
坑3:只存向量不存文本
collection.add(embeddings=vecs, ids=ids) → 搜出来只有 ID 和一串数字。
解法:始终同时传 documents 参数。
坑4:距离度量选错
文本语义用 l2(欧几里得) → 排序结果会很怪。
解法:文本用 cosine,图像用 l2,推荐系统用 ip(内积)。
坑5:Python 3.14 太新
torch、onnxruntime 等包没有 cp314 的 wheel,pip install 失败。
解法:uv python install 3.12 && uv venv --python 3.12
坑6:pip 指向系统 Python 而非 venv
pip install -r requirements.txt 装到了 Python 3.14 而不是 venv 的 3.12。
解法:uv pip install --python .venv/bin/python -r requirements.txt(强制指定解释器)