未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适合用于理解pgvector的核心定位,参考SQL与向量检索组合、索引调优、RAG调用及渐进迁移的设计思路;第十一节的小型向量脚本可在扩展已启用的环境中独立练习。第四节性能表及“真实QPS”缺少可定位、可复现的依据,第五节产品能力对比未限定版本与部署形态,第八节部分版本时间和性能提升数字也未提供出处,不能据此作生产容量、产品排名或选型承诺。决策树与成本分析中的“零新增复杂度”“部署成本最低”等结论不具有普遍适用性。各节SQL不是统一工程,混合查询需补充业务字段和关联表,第十二节建表依赖未提供的中文全文搜索配置,不能在默认PostgreSQL环境中直接完成所称完整实战。迁移类仅展示接口流程,生产使用需另行实现底层操作、失败恢复、更新删除同步、历史回填、一致性检查及回滚;迁移时间已限定为待验证估算。文末延伸阅读仅保留名称,不代表可访问材料。本次仅作全文结构与内部一致性审阅,未执行代码或开展外部事实核验。 已取消无法确定目标的可点击引用「向量数据库/pgvector/00-pgvector学习路线图」,保留原提及;Missing link target: 向量数据库/pgvector/00-pgvector学习路线图 已取消无法确定目标的可点击引用「向量数据库/00-向量数据库选型指南」,保留原提及;Missing link target: 向量数据库/00-向量数据库选型指南 已取消无法确定目标的可点击引用「向量数据库/Milvus」,保留原提及;Missing link target: 向量数据库/Milvus 已取消无法确定目标的可点击引用「向量数据库/Qdrant」,保留原提及;Missing link target: 向量数据库/Qdrant 已取消无法确定目标的可点击引用「AI与大数据/RAG应用架构」,保留原提及;Missing link target: AI与大数据/RAG应用架构
pgvector 概述与核心定位
一句话先说结论
对于已经使用PostgreSQL、希望复用关系数据与运维体系的团队,pgvector可以作为优先评估的向量搜索方案;本文不提供“80%的业务场景”这一比例的统计依据,也不作统一性能排名。
就像你不会为了偶尔搬一次家去买一辆卡车——打个出租车就够了,还省去了停车费、养车费和驾照年审的烦恼。
一、pgvector 到底是什么
先拆开这两个字:pg + vector
pg 是 PostgreSQL 的缩写,全球最受开发者欢迎的开源关系型数据库之一,已经存在超过 35 年。
vector 是向量,也就是 AI 模型(Embedding 模型)把文本、图片、音频转化后的那串数字——比如 [0.012, -0.341, 0.876, ...],通常几百到几千个数字。
pgvector 是一个 PostgreSQL 扩展(Extension),它给 PostgreSQL 加了一种新的数据类型(vector)和几种新的索引算法,让你可以在 PostgreSQL 里直接存储和搜索向量数据。
note 扩展是什么概念
PostgreSQL 的扩展机制类似于手机 App。手机出厂时有通话功能,你安装微信之后,手机多了即时通讯的能力。但微信跑在同一台手机上,用同一块电池,共享同一套权限系统。
pgvector 就是这样一个 App——安装后,你的 PostgreSQL 就拥有了向量存储和搜索能力,但它依然是同一个 PostgreSQL,用同一套存储引擎,共享同一个事务系统。
pgvector 不是什么
这比说"pgvector 是什么"更重要,因为很多人被名字误导了:
- pgvector 不是一个独立的数据库。它没有自己的进程,没有自己的端口,没有自己的配置文件。
- pgvector 不是 PostgreSQL 的竞争产品。它是 PostgreSQL 的一部分,离开 PostgreSQL 它什么都不是。
- pgvector 不是专为向量优化的系统。它的底层存储引擎是 PostgreSQL 的 MVCC 架构,这带来了事务一致性,但也带来了向量专用系统没有的额外开销。
把这三点搞清楚,你就能正确评估 pgvector 的边界了。
二、为什么 pgvector 会火
时机:AI 应用爆发的窗口期
2022 年底 ChatGPT 发布,2023 年全年,全球工程师都在疯狂构建 AI 应用。其中最典型的模式是 RAG(检索增强生成):
用户提问 → 把问题转成向量 → 在知识库里搜索最相关的文档 → 把文档和问题一起发给大模型 → 大模型回答
这个模式需要一个地方存储文档的向量,并快速检索。于是大家开始问:我应该用哪个向量数据库?
Pinecone、Weaviate、Chroma、Milvus……一堆新词涌出来。大多数工程师的第一反应是懵的:又要学一套新东西,又要维护一个新系统,又要处理新的认证、备份、监控……
这时候 pgvector 出现了,说:"你不用换数据库,你的 PostgreSQL 就能做。"
这句话,对于千千万万个"我们的业务数据本来就在 PostgreSQL 里"的团队来说,简直是天籁之音。
本质:复用 PostgreSQL 体系,减少新增服务复杂度
pgvector 火起来的核心原因,不是性能最好(它不是),不是功能最全(它也不是),而是——
它可以减少新增独立数据库服务的成本,但扩展部署、容量规划、索引维护和监控仍有成本。
你不需要:
- 学习新的 API(还是 SQL)
- 维护新的服务(还是 PostgreSQL 进程)
- 建立新的认证体系(还是 PostgreSQL 用户权限)
- 处理新的备份策略(还是 pg_dump / WAL 归档)
- 搭建新的监控(还是 pg_stat_statements)
- 建立新的主从复制(还是 Streaming Replication)
- 担心向量数据和业务数据的事务一致性(这是最大的优势之一)
对于一个已经在生产环境跑了 PostgreSQL 的团队,采用 pgvector 可以复用既有技能与部分基础设施,但仍需评估扩展部署、索引维护、容量、监控及业务改造成本。这种复用能力是它的重要优势。
云厂商的推波助澜
AWS RDS、Azure Database for PostgreSQL、Google Cloud SQL、Supabase、Neon……几乎所有主流的托管 PostgreSQL 服务都在 2023-2024 年内原生支持了 pgvector。
这意味着你甚至不需要手动安装,一行 CREATE EXTENSION vector 就搞定了。云厂商的背书极大地降低了试用门槛。
三、pgvector 能做什么
3.1 向量存储:新的数据类型
pgvector 引入了几种新的数据类型:
| 类型 | 说明 | 常用ANN索引维度或非零元素限制(非类型存储上限) | 适用场景 |
|---|---|---|---|
vector(n) |
标准单精度浮点向量 | 2000(ANN索引限制,非默认维度) | 通用场景,最常用 |
halfvec(n) |
半精度浮点向量(2字节) | 4000 | 内存敏感场景,精度损失极小 |
sparsevec(n) |
稀疏向量 | 1000 非零维 | BM25 等稀疏表示 |
bit(n) |
二值向量 | 64000 | 二值化哈希,极速粗排 |
-- 最常见的用法:1536 维浮点向量(OpenAI ada-002)
CREATE TABLE knowledge_base (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
source VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
embedding VECTOR(1536)
);
3.2 精确搜索(Sequential Scan / FLAT)
不建索引时,pgvector 会做全表扫描,计算每一行的向量距离,返回精确的 Top-K 结果。这叫精确搜索,召回率 100%,但速度随数据量线性下降。
-- 精确搜索:不需要索引,召回率 100%,适合小数据量(< 10万条)
SELECT id, title, embedding <=> $1::vector AS distance
FROM knowledge_base
ORDER BY embedding <=> $1::vector
LIMIT 10;
tip 什么时候用精确搜索
数据量在 5 万条以内时,精确搜索通常比近似搜索更快——因为建索引本身有开销,而全表扫描 5 万条只需要几十毫秒。不要过早优化。
3.3 近似搜索(ANN:Approximate Nearest Neighbor)
数据量大时,精确搜索太慢。pgvector 提供两种索引来做近似最近邻搜索,用少量精度损失换取大幅性能提升:
IVFFlat 索引(Inverted File with Flat quantization)
把向量空间划分成若干个簇,搜索时只在最相关的几个簇里查找。
-- 建立 IVFFlat 索引
-- lists 参数经验起点:数据量不超过100万时用 n/1000,超过100万时用 sqrt(n);再按召回率与延迟实测调整
CREATE INDEX idx_knowledge_embedding_ivf
ON knowledge_base
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- 搜索时指定 probes(搜多少个簇,越大越准但越慢)
SET ivfflat.probes = 10;
SELECT id, title, 1 - (embedding <=> $1::vector) AS similarity
FROM knowledge_base
ORDER BY embedding <=> $1::vector
LIMIT 10;
HNSW 索引(Hierarchical Navigable Small World)
构建多层图结构,用"高速路-省道-小路"的层级结构加速导航。
-- 建立 HNSW 索引
-- m:每个节点的最大连接数(推荐 16,范围 2-100)
-- ef_construction:构建时的搜索深度(推荐 64,范围 4-1000)
CREATE INDEX idx_knowledge_embedding_hnsw
ON knowledge_base
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 搜索时设置 ef_search(越大越准,推荐从 40 开始调)
SET hnsw.ef_search = 40;
3.4 混合查询:SQL + 向量的真正威力
这是 pgvector 相对于专用向量库最显著的差异化优势。
场景一:多租户隔离
-- 只在当前用户的知识库里搜索
SELECT k.id, k.title, k.content,
1 - (k.embedding <=> $1::vector) AS similarity
FROM knowledge_base k
WHERE k.tenant_id = $2 -- 租户隔离
AND k.is_deleted = false -- 软删除过滤
AND k.language = 'zh' -- 语言过滤
ORDER BY k.embedding <=> $1::vector
LIMIT 5;
场景二:时间范围限制
-- 只搜索最近 30 天的内容,且相关度必须超过阈值
SELECT k.id, k.title,
1 - (k.embedding <=> $1::vector) AS similarity
FROM knowledge_base k
WHERE k.created_at > NOW() - INTERVAL '30 days'
AND 1 - (k.embedding <=> $1::vector) > 0.7 -- 相似度阈值
ORDER BY k.embedding <=> $1::vector
LIMIT 10;
场景三:JOIN 关联查询
-- 把向量搜索结果和其他业务表 JOIN
SELECT
k.title,
k.content,
u.username AS author,
c.name AS category,
1 - (k.embedding <=> $1::vector) AS similarity
FROM knowledge_base k
JOIN users u ON k.author_id = u.id
JOIN categories c ON k.category_id = c.id
WHERE c.name IN ('技术', '产品')
ORDER BY k.embedding <=> $1::vector
LIMIT 10;
这些查询在专用向量库里要怎么实现?通常需要:先在向量库里搜索,拿到 ID 列表,再去关系库里查详情,再在应用层做过滤和 JOIN。不仅代码复杂,还涉及两个系统之间的网络往返和数据一致性问题。
3.5 距离度量选择
下表列出四种距离计算方式,选择时需结合向量类型、模型约定和索引支持:
| 运算符 | 计算方式 | 返回值范围 | 最佳适用 |
|---|---|---|---|
<-> |
L2 欧氏距离 | [0, +∞) | 图像特征、坐标数据 |
<=> |
余弦距离 | [0, 2] | 文本语义搜索(最常用) |
<#> |
负内积 | 可正可负;单位归一化向量为 [-1, 1] | 适用于内积检索;是否归一化应依据模型约定 |
<+> |
L1 曼哈顿距离 | [0, +∞) | 特定业务场景 |
note 余弦距离 vs 余弦相似度
pgvector 的 <=> 返回的是余弦距离(1 - 余弦相似度),值越小越相似。
如果你想要"相似度"(越大越好),用 1 - (embedding <=> query_vec) 转换。
非零向量的余弦相似度范围为 [-1, 1],归一化不会将其限制到 [0, 1];0表示正交,1表示同方向,-1表示反方向,语义相关性仍取决于模型和数据。
四、pgvector 和专用向量库的真实差距
这是最被过度讨论、也最被误解的问题。让我们用数据说话,而不是泛泛地说"专用库更好"。
4.1 性能对比:关键数字
基于 2024-2025 年的公开基准测试数据(ANN Benchmarks 及各厂商测试):
测试条件:100 万条 1536 维向量,余弦相似度,99% 召回率
| 系统 | QPS(每秒查询数) | P99 延迟 | 内存占用 | 索引构建时间 |
|---|---|---|---|---|
| pgvector HNSW | ~40 | ~25ms | ~1.4GB | ~68分钟 |
| pgvector IVFFlat | ~2.6 | ~385ms | ~500MB | ~2分钟 |
| Qdrant HNSW | ~800 | ~1.2ms | ~1.2GB | ~15分钟 |
| Milvus HNSW | ~1200 | ~0.8ms | ~1.6GB | ~10分钟 |
| pgvectorscale | ~471 | ~2.1ms | ~1.8GB | ~30分钟 |
warning 数字的上下文很重要
上表中的数字来自不同测试环境和版本,仅供量级参考,不可直接比较。实际差距受硬件配置、数据分布、查询模式影响极大。
更重要的问题是:你的业务真的需要 800 QPS 吗?
4.2 千万级以内:差距基本无感
来做一个现实检验。
假设你在构建一个企业内部知识库,有 50 万条文档,每天 1000 个用户使用,高峰期同时 100 人在查询。
- 峰值 QPS 需求:假设每次对话需要 3 次向量查询,100 个并发用户,每人每次等待 1 秒回复,那么 QPS 需求约为 300 次/秒
- pgvector HNSW 在 50 万条向量时的真实 QPS:配置好的情况下 100-200+ QPS
- 差距:可能不够,需要做些优化(缓存、读写分离、增加内存)
但换一个角度:
- 如果你用 Qdrant,QPS 没问题,但你现在需要维护两套数据库
- 你的业务数据(用户信息、文档元数据)在 PostgreSQL
- 你的向量数据在 Qdrant
- 每次查询都要跨两个系统协调,每次数据更新都要保证两边同步
- 当出现数据不一致时,你需要写复杂的补偿逻辑
这个额外的运维成本,值得换来的那 4-10 倍 QPS 提升吗?
对于大多数 100 万条以内的场景,答案是:不值得。
4.3 超过千万条:差距开始显现
当向量数量超过 1000 万条时,pgvector 的单机架构开始成为瓶颈:
- 内存压力:pgvector的HNSW索引持久化在磁盘上,不要求全部驻留内存,但缓存命中率会影响查询性能。1000万条1536维单精度向量仅原始数值部分就约61.44GB,尚未计入索引图结构、元组和其他开销,不能据此采用14GB索引与32GB机器的容量估算。
- 写入放大:HNSW 索引的维护在大规模高频写入时会成为瓶颈,每次插入都需要更新图结构。
- 查询时间:即使有 HNSW 索引,亿级别的数据下,单次查询的 P99 延迟会明显上升。
这时候,pgvectorscale(Timescale 出的增强扩展)或者直接迁移到专用向量库,才是合理的选项。
五、选 pgvector 还是专用向量库:横向对比
5.1 功能特性对比表
| 对比维度 | pgvector | Milvus | Qdrant | Chroma |
|---|---|---|---|---|
| 部署方式 | PostgreSQL 扩展 | 独立服务(K8s 原生) | 独立服务 | 独立服务 |
| SQL 支持 | 完整 SQL | 类 SQL(Milvus DSL) | REST/gRPC API | Python API |
| 事务支持 | 完整 ACID | 部分(不支持跨 Collection 事务) | 不支持 | 不支持 |
| 索引算法 | HNSW、IVFFlat | HNSW、IVF、DISKANN 等 | HNSW | HNSW |
| 最大向量规模 | 千万级(单机) | 百亿级(分布式) | 亿级 | 百万级(推荐) |
| 混合查询 | 原生 SQL JOIN | 支持,但需要 API | 支持 payload 过滤 | 支持 metadata 过滤 |
| 数据一致性 | 强一致(PostgreSQL WAL) | 最终一致 | 最终一致 | 最终一致 |
| 运维复杂度 | 低(复用 PG 运维体系) | 高(Etcd + MinIO + 多组件) | 中 | 低(纯内存/单文件) |
| 云托管支持 | 几乎所有主流云 | Zilliz Cloud | Qdrant Cloud | 无官方托管 |
| 多语言 SDK | 任何支持 PostgreSQL 的驱动 | Python/Go/Java/Node.js | Python/Rust/Go/etc | Python(主要) |
| License | PostgreSQL License(宽松) | Apache 2.0 | Apache 2.0 | Apache 2.0 |
| 适合团队规模 | 任何规模 | 中大型 | 中型 | 小型/原型 |
5.2 场景决策树
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
S["我需要向量搜索能力"] --> Q1{"已有 PostgreSQL?"}
Q1 --> |"是"| Q2{"向量数据量预期?"}
Q1 --> |"否,从零开始"| Q3{"向量是核心业务还是辅助功能?"}
Q2 --> |"< 100万条"| R1["强烈推荐 pgvector<br/>零新增复杂度"]
Q2 --> |"100万-1000万条"| R2["pgvector + 调优<br/>评估 pgvectorscale"]
Q2 --> |"> 1000万条"| Q4{"对延迟要求?"}
Q4 --> |"P99 < 10ms"| R3["考虑 Qdrant 或 Milvus"]
Q4 --> |"P99 < 100ms 可接受"| R4["pgvector + 硬件扩容<br/>或 pgvectorscale"]
Q3 --> |"辅助功能"| R5["pgvector(即使没有 PG)<br/>部署成本最低"]
Q3 --> |"核心业务<br/>高并发"| R6{"预期 QPS?"}
R6 --> |"< 500 QPS"| R7["pgvector 或 Qdrant"]
R6 --> |"> 500 QPS"| R8["Milvus 或 Qdrant"]
class Q1,Q2,Q3,Q4,R6 decision
class S,R1,R2,R3,R4,R5,R7,R8 default
5.3 成本维度的真实分析
很多文章只比较技术性能,忽视了总拥有成本(TCO)。
使用 pgvector 的成本结构:
- 基础设施:在现有 PostgreSQL 实例上加内存(向量索引内存需求)
- 运维人力:复用现有 DBA 团队的 PostgreSQL 技能,无新增学习曲线
- 应用改造:只需加几行建索引的 SQL,业务代码改动极小
引入专用向量库(以 Milvus 为例)的额外成本:
- 基础设施:Milvus 集群(至少需要 Etcd + MinIO + Query Node + Data Node + Index Node),通常需要 3-5 台机器
- 运维人力:团队需要学习 Milvus 的运维知识,分布式系统的 troubleshooting 成本高
- 数据同步:业务数据和向量数据需要保持同步,需要额外的 ETL 或 CDC 管道
- 监控接入:需要为新系统建立监控告警体系
对于一个 10 人以下的工程团队,这些隐性成本往往被严重低估。
六、选 pgvector 的真实场景
场景一:企业内部知识库(RAG 应用)
典型规模:10 万-500 万条文档,日活用户数百到数千
为什么选 pgvector:
- 文档元数据(作者、部门、权限、标签)本来就在 PostgreSQL
- 需要按权限过滤搜索结果(不同用户能看到的文档不同)
- 团队只有 2-3 个后端工程师,不想维护额外组件
实际案例模式:
CREATE TABLE enterprise_docs (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
dept_id INTEGER REFERENCES departments(id),
author_id INTEGER REFERENCES employees(id),
permission_groups INTEGER[], -- 有权限查看的组
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
embedding VECTOR(1536)
);
-- 带权限的语义搜索
SELECT d.title, d.content,
1 - (d.embedding <=> $1::vector) AS similarity
FROM enterprise_docs d
WHERE $2 = ANY(d.permission_groups) -- 权限过滤
ORDER BY d.embedding <=> $1::vector
LIMIT 5;
这个查询在 PostgreSQL 里天然支持,在专用向量库里需要额外实现权限过滤逻辑。
场景二:电商商品推荐
典型规模:10 万-500 万个 SKU,实时个性化推荐
为什么选 pgvector:
- 商品信息(库存、价格、分类、上下架状态)在 PostgreSQL
- 推荐结果需要结合实时库存过滤
- 业务逻辑复杂,需要频繁的 JOIN 查询
-- 找到与当前商品相似、且有库存、且在同价格范围的商品
SELECT
p.id, p.name, p.price,
1 - (p.embedding <=> $1::vector) AS similarity
FROM products p
WHERE p.status = 'active'
AND p.stock_quantity > 0
AND p.price BETWEEN $2 * 0.5 AND $2 * 2.0
AND p.category_id = $3
ORDER BY p.embedding <=> $1::vector
LIMIT 10;
场景三:客服工单智能路由
典型规模:百万级历史工单,实时分类路由
为什么选 pgvector:
- 工单本身就在 PostgreSQL
- 需要查询历史相似工单的处理结果
- 数据量完全在 pgvector 舒适区内
场景四:代码搜索(Code Search)
典型规模:中型项目,百万行以内代码片段
为什么选 pgvector:
- 代码元数据(仓库、文件路径、语言、作者)在关系库
- 搜索时需要按仓库、语言过滤
- 团队日常已经在用 PostgreSQL 管理代码索引
七、不选 pgvector 的真实场景
说清楚"什么时候不该用"同样重要。
场景一:数据量超过 1 亿条
这不是统一的硬边界。对于单实例部署,亿级向量需要特别评估维度、索引大小、缓存命中率、构建成本和目标延迟,以下风险应通过实际负载测试确认:
- HNSW 索引无法完全放入内存,性能急剧下降
- 索引构建时间以天计
- 查询 P99 延迟不可接受
建议:直接选 Milvus(百亿级分布式)或 Qdrant(亿级,内存效率更高)。
场景二:高并发实时写入(> 1000 向量/秒)
PostgreSQL 的 MVCC 机制在高频写入时会产生大量写放大,HNSW 索引的实时维护也有额外开销。若以1000向量/秒作为示例写入负载,需要结合向量维度、索引参数、硬件、事务批量和查询并发进行压测;它不是PostgreSQL性能明显下降的统一阈值。
建议:Qdrant 支持高并发写入,Milvus 通过分布式设计支持极高吞吐。
场景三:需要多模态向量搜索
pgvector 是通用向量扩展,不针对特定模态优化。如果你需要同时搜索文本向量、图像向量,并做跨模态检索,专用向量库(尤其是 Weaviate)有更好的支持。
场景四:向量是你唯一的数据
如果你的系统里根本没有关系型数据,所有东西都是向量,那 pgvector 带来的最大优势(混合查询)对你没有价值,你只是在用一个不那么好的向量系统。
这时候直接用专用向量库,简单直接。
八、版本演进:pgvector 在快速成熟
很多人对 pgvector 的印象还停留在"性能差"的早期版本。事实上,它在 2023-2025 年经历了跨越式的性能提升。
关键版本里程碑
| 版本 | 时间 | 重要特性 |
|---|---|---|
| v0.4.0 | 此处不作发布日期断言 | 尚未引入 HNSW 索引 |
| v0.5.0 | 此处不作发布日期断言 | 引入 HNSW 索引 |
| v0.6.0 | 2023 年底 | HNSW 并行构建(多核加速),性能跨越式提升 |
| v0.7.0 | 2024 年 | halfvec、sparsevec 新类型;HNSW 构建速度最高提升 30x |
| v0.8.0 | 2024 年底 | 迭代式索引扫描(解决过滤时的精度问题);filter 场景性能优化 |
| v0.8.2 | 本文未提供发布日期依据 | 本文源码安装示例固定使用的版本,不代表当前最新稳定版 |
note 150x 的故事
本文未提供可定位的博客依据,不能将“Jonathan Katz记录一年内整体性能提升超过150倍”作为真人经历或统一性能结论。即使某项测试达到150倍提升,也只能在明确基线版本、硬件、数据、查询任务与指标的条件下解释,不能推广为所有场景的整体提升。
v0.8.0 的重要修复:迭代式索引扫描
这个特性解决了一个长期存在的痛点:带 WHERE 过滤的向量搜索精度问题。
问题场景:
-- 你期望找 5 条结果,但过滤条件很严格
SELECT * FROM docs
WHERE category = 'tech' -- 只有 1% 的数据符合这个条件
ORDER BY embedding <=> $1::vector
LIMIT 5;
在旧版本中,HNSW 索引先找了 40 个候选(ef_search=40),但其中只有 2 个满足 category='tech',最终只返回 2 条,而不是期望的 5 条。这叫"过滤过度"(overfiltering)。
v0.8.0引入迭代式索引扫描;启用相应索引的iterative_scan设置后,可继续扩大扫描范围,但仍受扫描数量、内存及可用匹配数据限制,不保证一定返回LIMIT指定的结果数。
九、最佳实践速查
生产环境必配项
-- 1. 创建表时选择合适的向量类型
-- 数据量 < 500万且内存充足:vector(1536)
-- 数据量 > 500万或内存有限:halfvec(1536)
CREATE TABLE embeddings (
id BIGSERIAL PRIMARY KEY,
content TEXT,
embedding halfvec(1536) -- 内存友好
);
-- 2. 建 HNSW 索引前先调整内存
SET maintenance_work_mem = '4GB'; -- 越大,构建越快
SET max_parallel_maintenance_workers = 4; -- 并行构建
-- 3. 建索引(推荐参数起点)
CREATE INDEX CONCURRENTLY idx_emb_hnsw
ON embeddings
USING hnsw (embedding halfvec_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 4. 应用层设置查询参数
SET hnsw.ef_search = 40; -- 大多数场景的平衡点
-- 5. 监控索引使用情况
SELECT indexname, idx_scan, idx_tup_read
FROM pg_stat_user_indexes
WHERE tablename = 'embeddings';
常见陷阱和应对
陷阱一:未区分索引类型与批量导入阶段
初次批量导入通常可以先导入数据再建索引,以减少逐行维护索引的成本。IVFFlat应在已有代表性数据时构建,以形成有效聚类;HNSW不需要聚类训练,可以在空表上创建,但不意味着导入前建索引总是更优。是否REINDEX应根据数据分布变化与实测效果决定。
陷阱二:ef_search 设置太小
默认的 ef_search = 40 在高召回要求下可能不够。如果你的业务要求召回率 > 95%,可能需要 ef_search = 100 甚至 200。用不同参数跑基准测试,找到你业务的最佳平衡点。
陷阱三:忘记 ANALYZE
PostgreSQL 的查询规划器需要统计信息才能做出正确决策。大批量写入后,记得 ANALYZE embeddings; 更新统计信息,否则查询规划器可能做出错误选择(比如不走索引)。
陷阱四:向量维度不一致
一旦 CREATE TABLE 指定了向量维度,后续写入的向量必须完全匹配。如果你切换了 Embedding 模型(比如从 ada-002 切到 text-embedding-3-large),若实际输出维度从1536变为3072,需要迁移或调整向量列、重新生成匹配的嵌入并重建适用索引,不一定必须重建整张表;同时需注意vector类型的常用ANN索引维度限制。建议在应用层明确记录和校验维度。
陷阱五:不加向量搜索的相似度阈值
-- 危险:没有相似度阈值,即使最相关的结果也可能很不相关
SELECT * FROM docs ORDER BY embedding <=> $1 LIMIT 5;
-- 阈值示例:可过滤低分结果,但0.7需按模型与业务评估,不保证剩余结果相关
SELECT *, 1 - (embedding <=> $1) AS similarity
FROM docs
WHERE 1 - (embedding <=> $1) > 0.7 -- 余弦相似度分数需大于0.7;不表示70%的相关性或正确率
ORDER BY embedding <=> $1
LIMIT 5;
没有阈值时,向量搜索仍会返回距离排序最靠前的候选,即使它们在业务语义上不相关;返回数量可能少于LIMIT,例如可用数据不足或ANN过滤后候选不足时。
十、与 PostgreSQL 生态的协同
pgvector 的另一个被忽视的优势:它可以和 PostgreSQL 庞大的扩展生态协同工作。
pgvector + pg_trgm(模糊文本搜索)
-- 同时做语义搜索和关键词模糊匹配,取并集
SELECT id, title, embedding <=> $1 AS vec_dist,
similarity(title, $2) AS text_sim
FROM docs
WHERE similarity(title, $2) > 0.3 -- 关键词相似
OR embedding <=> $1 < 0.3 -- 语义相关
ORDER BY (embedding <=> $1) * 0.7 + (1 - similarity(title, $2)) * 0.3;
pgvector + TimescaleDB(时序向量)
处理带时间戳的向量数据(如传感器嵌入、日志分析),按时间分区后做向量搜索。
pgvector + PostGIS(空间向量)
把地理位置信息和语义向量结合,做"在 3 公里内、与我当前搜索语义最相关"的地理语义搜索。
pgvector + Row Level Security(行级安全)
利用 PostgreSQL 原生的行级安全策略,实现向量搜索结果的细粒度权限控制,这在专用向量库里几乎无法原生实现。
总结:给技术决策者的一句话
pgvector 的价值主张很清晰:如果你已经在用 PostgreSQL,可以优先评估复用它实现向量搜索;即使数据量在1000万条以内,也需结合维度、过滤条件、读写负载、目标延迟和运维能力判断成本与风险,不能保证它始终成本最低、风险最小。
不要因为专用向量库性能更好就盲目迁移。先问自己:
- 我的业务真的需要那 10 倍的 QPS 提升吗?
- 我的团队有能力维护一套额外的分布式系统吗?
- 业务数据和向量数据的同步成本,我算进去了吗?
- 当 pgvector 真的不够用时,迁移的成本有多高?(其实不高,因为向量是可以重新生成的)
大多数时候,答案指向的是:先用 pgvector,把产品做出来,等真的遇到性能瓶颈再迁移——到那时候,你已经有了足够的业务数据和技术判断力,知道该迁到哪里去。
十一、安装与快速验证
主流平台的安装方式
方式一:云数据库(推荐,零配置)
-- Supabase / Neon / AWS RDS / Azure PostgreSQL / Google Cloud SQL
-- 直接在 SQL 编辑器里执行:
CREATE EXTENSION IF NOT EXISTS vector;
-- 验证安装
SELECT extname, extversion FROM pg_extension WHERE extname = 'vector';
-- 结果示例:vector | 0.8.2
方式二:本地 macOS(Homebrew)
# 安装 PostgreSQL 和 pgvector
brew install postgresql@16
brew install pgvector
# 启动 PostgreSQL
brew services start postgresql@16
# 连接并启用扩展
psql -U postgres -c "CREATE EXTENSION vector;"
方式三:Docker(开发环境首选)
# 使用官方带 pgvector 的镜像
docker run -d \
--name pgvector-dev \
-e POSTGRES_PASSWORD=mypassword \
-e POSTGRES_DB=vectordb \
-p 5432:5432 \
pgvector/pgvector:pg16
# 连接
psql postgresql://postgres:mypassword@localhost:5432/vectordb -c "CREATE EXTENSION vector;"
方式四:Ubuntu/Debian 源码安装
# 安装依赖
apt-get install -y postgresql-server-dev-16 build-essential git
# 克隆并编译安装
git clone --branch v0.8.2 https://github.com/pgvector/pgvector.git
cd pgvector
make
make install
# 在 PostgreSQL 中启用
psql -U postgres -c "CREATE EXTENSION vector;"
快速验证脚本
安装完成后,用这个脚本验证一切正常:
-- 1. 创建测试表
CREATE TABLE test_vectors (
id SERIAL PRIMARY KEY,
label TEXT,
vec VECTOR(3)
);
-- 2. 插入测试数据
INSERT INTO test_vectors (label, vec) VALUES
('苹果', '[1.0, 0.1, 0.2]'),
('香蕉', '[0.9, 0.2, 0.1]'),
('汽车', '[0.1, 0.9, 0.8]'),
('火车', '[0.2, 0.8, 0.9]'),
('橙子', '[0.8, 0.3, 0.1]');
-- 3. 搜索与"苹果"最相似的 3 条(余弦距离)
SELECT label,
1 - (vec <=> '[1.0, 0.1, 0.2]'::vector) AS similarity
FROM test_vectors
ORDER BY vec <=> '[1.0, 0.1, 0.2]'::vector
LIMIT 3;
-- 预期结果:苹果、香蕉、橙子(都是水果,向量相近)
-- 4. 清理
DROP TABLE test_vectors;
十二、完整的 RAG 应用实战示例
这是一个从头到尾的 RAG(检索增强生成)实战,展示 pgvector 在最典型场景下的完整用法。
数据库准备
-- 知识库主表
CREATE TABLE kb_articles (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
source_url TEXT,
category VARCHAR(100),
tags TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW(),
embedding VECTOR(1536),
-- 用于全文搜索(可选,但推荐和向量搜索结合)
content_tsv TSVECTOR GENERATED ALWAYS AS (to_tsvector('chinese', content)) STORED
);
-- 搜索历史(用于分析热门问题)
CREATE TABLE search_history (
id BIGSERIAL PRIMARY KEY,
query_text TEXT,
query_vec VECTOR(1536),
result_ids BIGINT[],
user_id INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 建立 HNSW 索引
CREATE INDEX CONCURRENTLY idx_kb_embedding
ON kb_articles
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 建立全文搜索索引(混合搜索用)
CREATE INDEX idx_kb_fts ON kb_articles USING gin(content_tsv);
Python 应用层代码
import os
import openai
import psycopg2
import psycopg2.extras
from typing import Optional
# 初始化客户端
openai_client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
db_conn = psycopg2.connect(os.getenv("DATABASE_URL"))
def get_embedding(text: str) -> list[float]:
"""把文本转成向量"""
response = openai_client.embeddings.create(
input=text,
model="text-embedding-ada-002"
)
return response.data[0].embedding
def index_article(title: str, content: str,
source_url: str = None, category: str = None):
"""把文章写入知识库"""
embedding = get_embedding(f"{title}\n\n{content}")
with db_conn.cursor() as cur:
cur.execute("""
INSERT INTO kb_articles (title, content, source_url, category, embedding)
VALUES (%s, %s, %s, %s, %s)
RETURNING id
""", (title, content, source_url, category, embedding))
article_id = cur.fetchone()[0]
db_conn.commit()
return article_id
def semantic_search(
query: str,
category: Optional[str] = None,
limit: int = 5,
similarity_threshold: float = 0.7
) -> list[dict]:
"""语义搜索:找最相关的文章"""
query_vec = get_embedding(query)
# 动态构建 SQL,支持可选的 category 过滤
where_clauses = ["1 - (embedding <=> %s::vector) > %s"]
params = [query_vec, similarity_threshold]
if category:
where_clauses.append("category = %s")
params.append(category)
where_sql = " AND ".join(where_clauses)
params.append(query_vec) # ORDER BY 里用
params.append(limit)
sql = f"""
SELECT
id, title, content, source_url, category,
1 - (embedding <=> %s::vector) AS similarity
FROM kb_articles
WHERE {where_sql}
ORDER BY embedding <=> %s::vector
LIMIT %s
"""
with db_conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
# 重新整理参数顺序
cur.execute("""
SELECT
id, title, content, source_url, category,
1 - (embedding <=> %(vec)s::vector) AS similarity
FROM kb_articles
WHERE 1 - (embedding <=> %(vec)s::vector) > %(threshold)s
{category_filter}
ORDER BY embedding <=> %(vec)s::vector
LIMIT %(limit)s
""".format(
category_filter="AND category = %(category)s" if category else ""
), {
"vec": query_vec,
"threshold": similarity_threshold,
"category": category,
"limit": limit
})
return [dict(row) for row in cur.fetchall()]
def answer_with_rag(user_question: str) -> str:
"""完整的 RAG 流程:搜索 + 生成答案"""
# 第一步:语义搜索
relevant_docs = semantic_search(user_question, limit=3)
if not relevant_docs:
return "抱歉,我在知识库中没有找到相关内容。"
# 第二步:构建上下文
context = "\n\n---\n\n".join([
f"来源:{doc['title']}\n{doc['content']}"
for doc in relevant_docs
])
# 第三步:调用大模型生成答案
response = openai_client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "你是一个知识库助手。根据提供的参考资料回答问题,如果资料中没有答案,请说明。"
},
{
"role": "user",
"content": f"参考资料:\n\n{context}\n\n问题:{user_question}"
}
]
)
return response.choices[0].message.content
tip 生产环境的改进点
上面的代码是教学版本,生产环境还需要:
- 使用连接池(asyncpg + asyncio 或 psycopg3)
- 向量查询结果缓存(Redis)
- 批量写入时用
executemany或COPY命令提升性能 - Embedding API 调用的重试机制
- 记录搜索日志用于后续分析
十三、从 pgvector 迁移到专用向量库的路径
很多人担心:如果将来 pgvector 不够用了,迁移成本高不高?
答案是:比你想象的低得多。
为什么迁移相对容易
向量数据有一个特殊性:向量本身是可以重新生成的。你的真实数据(文本、图片原始内容)仍然在 PostgreSQL 里,向量只是数据的一种表示形式,随时可以用 Embedding 模型重新计算。
这意味着你不需要担心"数据怎么搬"——你只需要:
- 从 PostgreSQL 读取原始内容
- 重新调用 Embedding 模型生成向量
- 写入新的向量库
迁移时间估算
| 数据规模 | 重新生成 Embedding 时间(OpenAI API) | 写入向量库时间 | 总计 |
|---|---|---|---|
| 100 万条 | ~2-4 小时(批量 API 调用) | ~30 分钟 | ~3-5 小时 |
| 1000 万条 | ~20-40 小时 | ~3 小时 | ~1-2 天 |
| 1 亿条 | ~200 小时(需要并行) | ~30 小时 | ~1-2 周 |
对于大多数在 pgvector 上起步的应用,当真正达到需要迁移的规模时(1000 万条+),迁移时间表仅作为待验证的估算示例;在API额度、数据长度、目标库容量和团队资源满足要求时,可评估一个Sprint内完成及灰度切换的可行性,但不能保证工期或对线上业务无影响。
渐进式迁移方案
如果不想一次性全量迁移,可以采用双写策略:
class VectorSearchService:
"""双写渐进式迁移:同时写入 pgvector 和目标向量库"""
def __init__(self, pg_conn, qdrant_client, migration_mode=False):
self.pg = pg_conn
self.qdrant = qdrant_client
self.migration_mode = migration_mode # 迁移模式开关
def insert(self, doc_id: int, content: str, embedding: list):
# 始终写入 pgvector
self._pg_insert(doc_id, content, embedding)
# 迁移模式下同时写入 Qdrant
if self.migration_mode:
self._qdrant_insert(doc_id, content, embedding)
def search(self, query_vec: list, limit: int = 10) -> list:
if self.migration_mode:
# 对比两个系统的结果,验证一致性
pg_results = self._pg_search(query_vec, limit)
qdrant_results = self._qdrant_search(query_vec, limit)
self._log_diff(pg_results, qdrant_results)
# 迁移完成前,仍然以 pgvector 结果为准
return pg_results
else:
return self._pg_search(query_vec, limit)
这种方式展示双写与结果对比的设计思路;只有补充写入失败恢复、更新删除同步、历史回填、一致性检查和回滚机制后,才能评估生产验证与切流的安全性。
总结:给技术决策者的一句话
- pgvector 学习路线图 — 系统性的学习计划
- 向量数据库选型指南 — 横向比较所有主流方案
- Milvus 完整指南 — 当规模超出 pgvector 能力时的选择
- Qdrant 完整指南 — 性能和运维复杂度的折中方案
- RAG 应用架构设计 — pgvector 最典型的落地场景
#pgvector #向量数据库 #PostgreSQL #AI应用 #语义搜索 #RAG #向量搜索 #数据库选型