“找出所有与这张图片相似的商品。”
“搜索语义上相关的文档,而不只是关键词匹配。”
“推荐用户可能喜欢的内容。”
这些需求有什么共同点?它们都需要找**“相似”而不是“相等”**的数据。传统数据库在这里彻底无能为力了。
从精确匹配到相似搜索
传统数据库擅长的是精确匹配:
-- 这个,数据库很擅长
SELECT * FROM users WHERE email = 'alice@example.com';
-- 这个,也行
SELECT * FROM products WHERE name LIKE '%手机%';
-- 但这个?
-- "找出所有和这张猫的图片相似的图片"
-- "找出语义上和这段文字意思相近的文档"
-- 传统数据库:???
问题的核心是:如何衡量"相似"?
向量:万物的数学表示
答案是把一切都转换成向量(一组数字):
向量化(Embedding):
"可爱的小猫" → [0.12, -0.45, 0.78, ..., 0.33] (1536 维)
"adorable kitten" → [0.11, -0.43, 0.79, ..., 0.35] (1536 维)
"enterprise software" → [-0.82, 0.15, -0.23, ..., 0.91] (1536 维)
语义相近的内容,向量也相近
语义不同的内容,向量差异大
这就是**嵌入向量(Embedding)**的魔力。大语言模型和专门的嵌入模型能把任何东西——文本、图片、音频——转换成固定长度的向量。
向量的来源:
文本嵌入:
OpenAI text-embedding-3-small → 1536 维
BERT → 768 维
BGE → 1024 维
图片嵌入:
CLIP → 512 维
ResNet → 2048 维
音频嵌入:
Whisper → 512 维
相似度计算
两个向量有多"相似"?常用的度量方式:
1. 余弦相似度(Cosine Similarity)
衡量方向的相似性,忽略长度
范围:-1 到 1,1 表示完全相同方向
适合:文本语义搜索
2. 欧几里得距离(L2 Distance)
空间中的直线距离
范围:0 到无穷,0 表示完全相同
适合:图像搜索
3. 内积(Inner Product / Dot Product)
综合考虑方向和长度
适合:推荐系统(向量已归一化时等价于余弦相似度)
可视化:
向量 A = [1, 0]
向量 B = [0.9, 0.1] ← 方向相近
向量 C = [-1, 0] ← 方向相反
B
↗
A ────→
←──── C
A 和 B 的余弦相似度 ≈ 0.99(很相似)
A 和 C 的余弦相似度 = -1(完全相反)
暴力搜索的困境
最直接的方法是和所有向量逐一比较:
暴力搜索(Brute Force):
查询向量 q,数据库有 N 个向量
for i in 1..N:
distance[i] = compute_distance(q, vectors[i])
return top_k_smallest(distance)
时间复杂度:O(N × D)
N = 向量数量
D = 向量维度
问题:当 N = 1 亿,D = 1536 时,每次搜索需要计算 1536 亿次浮点运算。即使用 GPU 加速,延迟也无法接受。
类比传统数据库:
精确查找:
有索引 → O(log N)
无索引 → O(N)
向量搜索:
需要专门的向量索引
否则每次都是全表扫描
向量索引:用精度换速度
向量索引的核心思想:放弃 100% 精确,换取数量级的速度提升。
IVF(倒排文件索引)
IVF (Inverted File) 原理:
1. 聚类:把向量空间分成若干区域(聚类中心)
┌─────────────────────────────┐
│ • C1 • │
│ • • • • • • │
│ C2 C3 │
│ • • • • • • • │
│ │
└─────────────────────────────┘
2. 搜索时:先找最近的几个聚类中心,只在这些区域内搜索
查询 q 最近的中心是 C2
→ 只搜索 C2 区域内的向量
→ 搜索范围从 N 缩小到 N/num_clusters
HNSW(分层可导航小世界图)
这是目前最流行的向量索引:
HNSW (Hierarchical Navigable Small World) 原理:
多层跳表式结构:
Layer 2: A ──────────────────── D
│ │
Layer 1: A ──── B ──────── C ── D
│ │ │ │
Layer 0: A ─ B ─ C ─ E ─ F ─ G ─ D
搜索过程:
1. 从顶层开始,粗粒度定位
2. 逐层下降,细粒度搜索
3. 最底层得到精确结果
优点:
- 搜索复杂度 O(log N)
- 召回率高(通常 > 95%)
- 不需要训练
索引对比
┌─────────┬─────────────┬───────────┬──────────────┐
│ 索引 │ 构建速度 │ 查询速度 │ 内存占用 │
├─────────┼─────────────┼───────────┼──────────────┤
│ Flat │ 即时 │ 最慢 │ 最小 │
│ IVF │ 需要训练 │ 中等 │ 中等 │
│ HNSW │ 较慢 │ 最快 │ 较大 │
└─────────┴─────────────┴───────────┴──────────────┘
选择建议:
- 数据量小(< 10万):Flat 就够了
- 数据量中(10万-1000万):HNSW
- 数据量大(> 1000万):IVF + 量化压缩
专用向量数据库
Milvus:云原生向量数据库
Milvus 架构:
┌────────────────────────────────────────────┐
│ Milvus │
├────────────────────────────────────────────┤
│ Query Node │ Data Node │ Index Node │
│ (查询) │ (存储) │ (索引) │
├────────────────────────────────────────────┤
│ Meta Store (etcd) │
│ Object Storage (S3/MinIO) │
└────────────────────────────────────────────┘
特点:
- 分布式,水平扩展
- 支持多种索引类型
- 云原生设计
Pinecone:全托管向量服务
Pinecone:
优点:
- 全托管,零运维
- 开箱即用
- 性能优秀
缺点:
- 只有云服务,无法自部署
- 价格较高
- 数据在第三方
Qdrant:Rust 编写的高性能方案
Qdrant:
- Rust 编写,性能优秀
- 支持丰富的过滤条件
- 提供云服务和自部署
- 活跃的开源社区
pgvector:PostgreSQL 的向量扩展
又是 PostgreSQL!没错,PostgreSQL 通过 pgvector 扩展也支持向量搜索了。
基本使用
-- 安装扩展
CREATE EXTENSION vector;
-- 创建包含向量的表
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
title TEXT,
content TEXT,
embedding vector(1536) -- 1536 维向量
);
-- 插入数据
INSERT INTO documents (title, content, embedding)
VALUES (
'PostgreSQL简介',
'PostgreSQL 是一个强大的开源关系数据库...',
'[0.12, -0.45, 0.78, ...]'::vector
);
向量搜索
-- 余弦相似度搜索(<=> 操作符)
SELECT title, content,
1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 10;
-- L2 距离搜索(<-> 操作符)
SELECT title, embedding <-> '[0.1, 0.2, ...]'::vector AS distance
FROM documents
ORDER BY embedding <-> '[0.1, 0.2, ...]'::vector
LIMIT 10;
-- 内积搜索(<#> 操作符,注意是负的)
SELECT title, (embedding <#> '[0.1, 0.2, ...]'::vector) * -1 AS inner_product
FROM documents
ORDER BY embedding <#> '[0.1, 0.2, ...]'::vector
LIMIT 10;
创建向量索引
-- HNSW 索引(推荐,性能最好)
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- IVFFlat 索引(构建更快,适合频繁更新的场景)
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- 索引操作符说明:
-- vector_cosine_ops: 余弦距离
-- vector_l2_ops: 欧几里得距离
-- vector_ip_ops: 内积
结合传统查询
这是 pgvector 最大的优势——可以和 SQL 无缝结合:
-- 向量搜索 + 传统过滤
SELECT title, content
FROM documents
WHERE category = 'technology' -- 传统过滤
AND created_at > '2024-01-01' -- 传统过滤
ORDER BY embedding <=> $1::vector -- 向量排序
LIMIT 10;
-- 向量搜索 + JOIN
SELECT d.title, c.name AS category
FROM documents d
JOIN categories c ON d.category_id = c.id
WHERE d.embedding <=> $1::vector < 0.5 -- 相似度阈值
ORDER BY d.embedding <=> $1::vector
LIMIT 10;
-- 向量搜索 + 全文搜索混合
SELECT title,
ts_rank(to_tsvector(content), to_tsquery('postgresql')) AS text_rank,
1 - (embedding <=> $1::vector) AS vector_similarity
FROM documents
WHERE to_tsvector(content) @@ to_tsquery('postgresql')
ORDER BY vector_similarity DESC
LIMIT 10;
Rust 实战:RAG 应用
RAG(Retrieval-Augmented Generation)是 AI 应用的热门架构:
use sqlx::{PgPool, postgres::PgPoolOptions};
use pgvector::Vector;
use anyhow::Result;
#[derive(Debug, sqlx::FromRow)]
struct Document {
id: i32,
title: String,
content: String,
similarity: f64,
}
struct KnowledgeBase {
pool: PgPool,
}
impl KnowledgeBase {
async fn new(database_url: &str) -> Result<Self> {
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(database_url)
.await?;
Ok(Self { pool })
}
/// 添加文档到知识库
async fn add_document(
&self,
title: &str,
content: &str,
embedding: &[f32],
) -> Result<i32> {
let vector = Vector::from(embedding.to_vec());
let id = sqlx::query_scalar!(
r#"
INSERT INTO documents (title, content, embedding)
VALUES ($1, $2, $3)
RETURNING id
"#,
title,
content,
vector as Vector
)
.fetch_one(&self.pool)
.await?;
Ok(id)
}
/// 语义搜索:找到最相关的文档
async fn search(
&self,
query_embedding: &[f32],
limit: i32,
) -> Result<Vec<Document>> {
let vector = Vector::from(query_embedding.to_vec());
let docs = sqlx::query_as!(
Document,
r#"
SELECT
id,
title,
content,
1 - (embedding <=> $1::vector) AS "similarity!"
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT $2
"#,
vector as Vector,
limit
)
.fetch_all(&self.pool)
.await?;
Ok(docs)
}
/// 带过滤条件的混合搜索
async fn hybrid_search(
&self,
query_embedding: &[f32],
keyword: &str,
category: &str,
limit: i32,
) -> Result<Vec<Document>> {
let vector = Vector::from(query_embedding.to_vec());
// 注意:中文全文搜索需要安装 zhparser 或 pg_jieba 扩展
// 这里使用 simple 配置作为示例
let docs = sqlx::query_as!(
Document,
r#"
SELECT
d.id,
d.title,
d.content,
1 - (d.embedding <=> $1::vector) AS "similarity!"
FROM documents d
WHERE d.category = $2
AND to_tsvector('simple', d.content) @@ plainto_tsquery('simple', $3)
ORDER BY d.embedding <=> $1::vector
LIMIT $4
"#,
vector as Vector,
category,
keyword,
limit
)
.fetch_all(&self.pool)
.await?;
Ok(docs)
}
}
/// RAG 流程示例
async fn rag_query(
kb: &KnowledgeBase,
user_question: &str,
embedding_client: &impl EmbeddingClient,
llm_client: &impl LLMClient,
) -> Result<String> {
// 1. 将问题转换为向量
let question_embedding = embedding_client
.embed(user_question)
.await?;
// 2. 检索相关文档
let relevant_docs = kb
.search(&question_embedding, 5)
.await?;
// 3. 构建上下文
let context = relevant_docs
.iter()
.map(|d| format!("标题:{}\n内容:{}", d.title, d.content))
.collect::<Vec<_>>()
.join("\n\n---\n\n");
// 4. 调用 LLM 生成回答
let prompt = format!(
"基于以下参考资料回答问题。\n\n\
参考资料:\n{}\n\n\
问题:{}\n\n\
回答:",
context, user_question
);
let answer = llm_client.generate(&prompt).await?;
Ok(answer)
}
// 特征定义
#[async_trait::async_trait]
trait EmbeddingClient {
async fn embed(&self, text: &str) -> Result<Vec<f32>>;
}
#[async_trait::async_trait]
trait LLMClient {
async fn generate(&self, prompt: &str) -> Result<String>;
}
pgvector 的局限
pgvector 的限制:
1. 性能天花板
- 单机性能有限
- 超过 1000 万向量开始吃力
- 无法像 Milvus 那样分布式扩展
2. 索引构建
- HNSW 索引构建较慢
- 大数据集需要足够内存
3. 高级功能缺失
- 没有量化压缩(减少内存占用)
- 没有 GPU 加速支持
适用场景:
✓ 向量数量 < 1000 万
✓ 已有 PostgreSQL 技术栈
✓ 需要向量与业务数据关联
✓ 对成本敏感(不想引入新组件)
不适用:
✗ 超大规模向量搜索(> 1 亿)
✗ 极低延迟要求(< 10ms)
✗ 需要分布式架构
向量数据库选型
选 pgvector 如果:
✓ 数据量适中(< 1000 万向量)
✓ 已有 PostgreSQL
✓ 需要 SQL 能力和事务
✓ 成本敏感
选 Qdrant 如果:
✓ 需要高性能
✓ 可以自部署
✓ 需要丰富的过滤能力
✓ Rust 技术栈
选 Milvus 如果:
✓ 超大规模数据
✓ 需要分布式
✓ 已有 Kubernetes 基础设施
选 Pinecone 如果:
✓ 全托管,零运维
✓ 快速上线
✓ 预算充足
核心认知
向量数据库的本质:把"相似"这个模糊的概念,转化为向量空间中"距离"这个精确的数学问题。
AI 时代的数据库趋势:
传统应用:
用户请求 → 精确查询 → 返回结果
"给我 ID=123 的订单"
AI 应用:
用户请求 → 语义理解 → 向量搜索 → 上下文构建 → LLM 生成
"帮我找找类似这个风格的产品"
数据库需要同时支持:
- 精确查询(关系模型)
- 模糊匹配(全文搜索)
- 语义搜索(向量搜索)
PostgreSQL + pgvector 提供了一站式解决方案
下一篇是本系列的终章。我们将回顾 PostgreSQL 如何通过扩展机制,一步步成为能够应对几乎所有场景的"瑞士军刀"——从关系型到文档、从时序到向量,一个数据库的文艺复兴。
上一篇:时序数据库——时间维度的专家
本系列: