pgvector 向量相似度搜索
High Contrast
Dark Mode
Light Mode
Sepia
Forest
1 min read261 words

pgvector 向量相似度搜索

传统全文搜索依赖关键词匹配——搜索"数据库优化"找不到"查询提速"相关文章。pgvector 让 PostgreSQL 存储 AI 嵌入向量(Embedding),实现语义搜索:即使用户的词和文档里的词不重叠,也能找到语义相近的内容。


向量搜索的原理

文本 → Embedding 模型 → 向量(例如 1536 维的浮点数组)
"数据库索引优化" → [0.23, -0.15, 0.87, ..., 0.04]  ← 1536 个数字
"查询性能提升技巧" → [0.21, -0.12, 0.85, ..., 0.06]  ← 语义相近,向量也相近
相似度 = 余弦相似度(Cosine Similarity)= 向量夹角的余弦值
越接近 1 → 语义越相似

安装 pgvector

# Ubuntu
sudo apt install postgresql-16-pgvector
# macOS (Homebrew)
brew install pgvector
# Docker(使用官方 pgvector 镜像)
docker pull pgvector/pgvector:pg16
# docker-compose.yml 中:
# image: pgvector/pgvector:pg16
-- 在数据库中启用
CREATE EXTENSION vector;
-- 验证
SELECT '[1,2,3]'::vector;  -- 测试向量类型

创建向量表

-- 存储文章及其嵌入向量
CREATE TABLE articles (
id         BIGSERIAL PRIMARY KEY,
title      TEXT NOT NULL,
content    TEXT,
embedding  VECTOR(1536),   -- OpenAI text-embedding-3-small 是 1536 维
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 其他常见维度:
-- OpenAI text-embedding-3-small: 1536 维
-- OpenAI text-embedding-3-large: 3072 维
-- Anthropic claude embeddings: 1024 维
-- sentence-transformers (本地): 384-768 维

插入向量数据

# Python:用 OpenAI API 生成 Embedding 并存入 PostgreSQL
import openai
import psycopg2
client = openai.OpenAI()
def get_embedding(text: str) -> list[float]:
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding  # 返回 1536 维的列表
# 连接数据库
conn = psycopg2.connect("postgresql://user:pass@localhost/mydb")
cur = conn.cursor()
# 批量插入(生产环境批量处理)
articles = [
("PostgreSQL 索引策略", "B-tree、GIN、GiST..."),
("数据库性能优化", "EXPLAIN ANALYZE..."),
]
for title, content in articles:
embedding = get_embedding(f"{title}: {content}")
cur.execute(
"INSERT INTO articles (title, content, embedding) VALUES (%s, %s, %s)",
(title, content, embedding)
)
conn.commit()
-- 或者直接在 SQL 中插入(测试用)
INSERT INTO articles (title, content, embedding)
VALUES (
'测试文章',
'测试内容',
'[0.1, 0.2, 0.3, ...]'::vector  -- 实际需要 1536 个值
);

向量相似度搜索

-- 三种距离度量:
-- <->  欧几里得距离(L2 距离,值越小越相似)
-- <#>  负内积(值越小越相似)
-- <=>  余弦距离(1 - 余弦相似度,值越小越相似)
-- 余弦距离(最常用于文本语义搜索)
SELECT
id,
title,
1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS cosine_similarity
FROM articles
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 10;
-- 实际查询:先获取查询向量,再搜索
-- (在应用层生成 query_embedding,然后执行 SQL)
# Python:完整语义搜索流程
def semantic_search(query: str, limit: int = 10):
# 1. 生成查询向量
query_embedding = get_embedding(query)
# 2. 在 PostgreSQL 中搜索
cur.execute("""
SELECT
id,
title,
content,
1 - (embedding <=> %s::vector) AS similarity
FROM articles
WHERE embedding IS NOT NULL
ORDER BY embedding <=> %s::vector
LIMIT %s
""", (query_embedding, query_embedding, limit))
return cur.fetchall()
# 使用
results = semantic_search("如何提升数据库查询速度")
# 即使文章标题是"PostgreSQL 索引策略",语义相近也会被找到

创建向量索引(IVFFlat 和 HNSW)

没有索引时向量搜索是精确但慢的全表扫描。pgvector 提供两种近似最近邻(ANN)索引:

-- 方法一:IVFFlat(Inverted File Flat)
-- 先用 K-Means 聚类,查询时只搜索最近的几个簇
-- 要求:建索引前表中要有足够的数据(至少 lists * 39 行)
CREATE INDEX idx_articles_ivfflat
ON articles USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- lists:簇的数量,推荐 = sqrt(总行数)
-- 10 万行 → lists = 100
-- 100 万行 → lists = 1000
-- 查询时设置 probes(查询的簇数,越大越准确但越慢)
SET ivfflat.probes = 10;  -- 默认 1,推荐 sqrt(lists)
-- 方法二:HNSW(Hierarchical Navigable Small World)(PostgreSQL pgvector 0.5+)
-- 更高精度,查询更快,但建索引慢、内存占用大
CREATE INDEX idx_articles_hnsw
ON articles USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- m:每个节点的最大连接数(推荐 16)
-- ef_construction:建索引时的搜索宽度(推荐 64-128)
-- 查询时设置 ef_search
SET hnsw.ef_search = 40;  -- 默认 40,增大提高精度
-- 选择哪种索引?
-- 数据量 < 100 万:IVFFlat(建索引快,资源少)
-- 数据量 > 100 万 或 精度要求高:HNSW(查询快,精度高)

混合搜索:向量 + 关键词

-- 实际场景:结合全文搜索和向量搜索,提高召回率和精度
-- 方法一:先向量搜索,再全文过滤
SELECT
id,
title,
1 - (embedding <=> :query_embedding) AS similarity
FROM articles
WHERE search_vector @@ plainto_tsquery('chinese', :keyword)  -- 全文过滤
ORDER BY embedding <=> :query_embedding  -- 向量排序
LIMIT 20;
-- 方法二:Reciprocal Rank Fusion(RRF)融合排名
-- 同时获得向量排名和全文排名,取加权平均
WITH vector_results AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> :query_embedding) AS vrank
FROM articles
LIMIT 50
),
text_results AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(search_vector, query) DESC) AS trank
FROM articles, plainto_tsquery('chinese', :keyword) AS query
WHERE search_vector @@ query
LIMIT 50
)
SELECT
COALESCE(v.id, t.id) AS id,
1.0 / (60 + COALESCE(v.vrank, 100)) + 1.0 / (60 + COALESCE(t.trank, 100)) AS rrf_score
FROM vector_results v
FULL OUTER JOIN text_results t ON v.id = t.id
ORDER BY rrf_score DESC
LIMIT 10;

实战:RAG(检索增强生成)场景

-- RAG:Retrieval-Augmented Generation
-- 把用户问题转为向量,搜索相关文档,作为上下文发给 LLM
-- 知识库表
CREATE TABLE knowledge_base (
id         BIGSERIAL PRIMARY KEY,
source     TEXT NOT NULL,        -- 文档来源
chunk_text TEXT NOT NULL,        -- 文档片段(通常 200-500 词)
chunk_seq  INTEGER NOT NULL,     -- 片段在原文中的顺序
embedding  VECTOR(1536),
metadata   JSONB DEFAULT '{}'
);
CREATE INDEX idx_kb_hnsw ON knowledge_base USING hnsw (embedding vector_cosine_ops);
CREATE INDEX idx_kb_source ON knowledge_base (source);
-- 检索相关片段(Python 调用时替换 $query_embedding)
SELECT
chunk_text,
source,
1 - (embedding <=> $1::vector) AS relevance
FROM knowledge_base
ORDER BY embedding <=> $1::vector
LIMIT 5;
-- 返回的 5 个片段作为 LLM 的上下文 (context)

性能监控

-- 查看向量索引使用情况
SELECT
indexname,
idx_scan,
idx_tup_read,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE indexname LIKE '%hnsw%' OR indexname LIKE '%ivfflat%';
-- HNSW 索引通常比原始数据大 1.5-2 倍(内存要求高)
-- IVFFlat 索引通常比原始数据小 10-30%

下一章备份、恢复与高可用——全文搜索和向量搜索都搭建好了,现在确保数据不会丢失——pg_dump 增量备份、流复制与自动故障转移。