pgvector单节点可处理数千万向量,与主数据库共存、无需同步,适合大多数生产RAG场景。
过去两年间,"我们在做 RAG"悄无声息地变成了"所以我们需要向量数据库",而后半句话从此不再被质疑。你把 Pinecone 或 Qdrant 或 Weaviate 加入技术栈,接上同步任务,现在你拥有了两个必须永远保持一致的数据存储。用于演示,可以理解。用于大多数生产系统,你只是买了一个分布式系统问题来解决一个你本来没有的问题。
一个数字本该在争论开始前就终结这场讨论:pgvector,这个运行在你已经在为之付费的 Postgres 内部的向量扩展,在单节点上轻松处理低则数千万向量的向量搜索。不是"对于玩具项目而言"。是数千万级别。而且它是在你的 embeddings 与其他数据共享同一事务、同一备份、同一条 WHERE tenant_id = ? 条件的情况下做到的。专用向量数据库是一个真正的工具,有真正用武之地。只是那个用武之地在规模曲线上要比卖它的人想让你相信的位置高得多。
剥掉营销话术,向量数据库只做一件事:对高维 embeddings 做近似最近邻搜索。你有一个查询向量,你有数百万个存储向量,你想通过余弦或 L2 距离找出最接近的几个,而无需与每一个做比较。使其快速的诀窍是索引,几乎总是 HNSW(Hierarchical Navigable Small World),这是一个图,你沿着它以大约对数时间找到近邻,而不是扫描整个集合。
就这样。这就是核心奥妙。而 Postgres 自 2023 年 pgvector 0.5.0 推出 HNSW 以来就拥有了它。以下是大多数应用需要的完整"向量数据库":
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
doc_id bigint NOT NULL REFERENCES documents(id),
tenant_id bigint NOT NULL,
content text,
embedding vector(1536) -- e.g. OpenAI text-embedding-3-small
);
-- The index that makes it fast. m and ef_construction trade build time for recall.
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
最近邻查询就是一个带距离操作符的 ORDER BY(<=> 是余弦距离,<-> 是 L2,<#> 是负内积):
SELECT id, content
FROM chunks
ORDER BY embedding <=> $1 -- $1 is your query embedding
LIMIT 10;
这个查询,在内存中能容纳的 HNSW 索引上执行,在大多数团队运行的规模下,不会比在 Pinecone 上执行有明显变慢。基准测试中,pgvector 配合 HNSW 在约 100 万向量时达到或超越了专用引擎。所以诚实的问题不是"pgvector 够不够好"——在百万向量级它显然够。问题是你不引入第二套系统会失去什么,答案是:什么也不会失去。你反而会得到东西。
这是比较图表没有列入的部分,因为它放不进 QPS 那一栏。当你的向量住在 Postgres 里,Postgres 做的其他所有事情同时适用于它们。
过滤和多租户就是一条 WHERE 子句。真正的 RAG 几乎从不是"搜索所有向量"。而是"搜索这个租户的文档",或者"搜索这个用户能看到的、近 90 天内的、处于'已发布'状态的文档"。在 Postgres 里这是你本来就熟悉的查询,它在同一次索引扫描中运行:
SELECT c.id, d.title, c.content
FROM chunks c
JOIN documents d ON d.id = c.doc_id
WHERE c.tenant_id = $1
AND d.status = 'published'
ORDER BY c.embedding <=> $2
LIMIT 10;
看看那个 JOIN。你的 embedding 结果返回时已经与文档的标题、作者、权限、需要的一切缝合在一起,一趟往返。在专用向量存储中你拿回的是一个 ID 列表,然后你再向 Postgres 发第二次请求来填充它们,现在你在应用代码里手工做一个分布式 JOIN,还寄希望于两套系统没有漂移。
行级安全意味着你无法忽视的租户隔离。你可以把这个租户隔离下推到数据库层,这样一条缺失的 WHERE 子句不会把一个客户的数据泄露到另一个客户的搜索结果中:
ALTER TABLE chunks ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON chunks
USING (tenant_id = current_setting('app.tenant_id')::bigint);
试试在分离的向量数据库中强制执行这个。在存储层你做不到,真的。租户隔离变成了一个你需要在应用层重新实现和重新审计的东西,而这恰恰是演变成安全事件的那类事情。
一个事务、一个备份、一个真相。当你插入一个文档及其 chunks 及其 embeddings,那是一个事务。它提交或者不提交。不存在文档存在但其向量不存在的窗口,也不存在你删了一条记录但其 embedding 仍然漂浮在另一个系统中、在搜索里返回鬼魂的窗口。你现有的备份、现有的副本、现有的时间点恢复已经覆盖了向量。你没有增加运维面。你只是加了一列。
"一个真相"这一点就是整个论证的核心。真的。一旦你把向量拆分到独立的存储,你就背上了一个同步问题:双写、你的真相来源和搜索索引之间的最终一致性、对账任务,以及凌晨三点灵魂拷问——为什么一条已删除的记录仍然出现在 RAG 结果里。这个问题是真实的工作量,而你承担它只是为了节省一个你还无法测量的延迟差异。

pgvector 不是魔法,假装它没有尖锐边缘是让你出于错误原因退回专用数据库阵营的方式。真正会咬人的恰好是两件事,而且都有答案。
坑一:HNSW 索引必须能放进内存。pgvector 性能最大的单一因素是 HNSW 图是否位于 RAM 中。当索引 fits 进 shared_buffers 并保持在那里,查询又快又无聊。当它因为索引超出内存或被负载挤出而溢出到磁盘,尾延迟就一头栽下悬崖。所以为 pgvector 做容量规划实际上就是内存规划:了解你的向量数乘以维度数乘以索引开销,确保有裕量地容纳下它。一个 1536 维向量原始大小约 6KB;一千万个加上 HNSW 开销在 2026 年的数据库服务器上是一个真实但非常普通的内存量。这里 halfvec 也值得一用:用 16 位浮点数存储 embeddings,内存大致减半,对于 3072 维的模型这就是能塞进去和塞不进去的差别。
坑二:过滤搜索以前会悄悄返回太少的行。这个坑烧了人们好多年,是最常见的"pgvector 坏了"投诉。在 pgvector 0.8.0 之前,HNSW 索引先返回候选集,然后你的 WHERE tenant_id = ? 过滤在那个集合上执行。如果你的过滤条件很严格,你会要 10 个结果但只拿到 3 个,因为索引候选集中有 7 个属于其他租户、在事后被丢弃了。它看起来像一个正确性 bug 但实际是操作顺序的问题。
pgvector 0.8.0 用迭代索引扫描修复了它:查询规划器持续从索引中拉取更多数据,直到有足够行通过你的过滤。你在每个会话中启用它:
SET hnsw.iterative_scan = relaxed_order; -- keep scanning until LIMIT is satisfied
-- strict_order preserves exact distance order; relaxed_order trades a little
-- ordering for better recall under selective filters.
-- Bounds: hnsw.max_scan_tuples (default 20000), hnsw.scan_mem_multiplier (default 1).
SELECT id, content
FROM chunks
WHERE tenant_id = $1
ORDER BY embedding <=> $2
LIMIT 10;
如果你在 0.8.0 之前评估过 pgvector,撞上了过滤搜索的这堵墙,然后得出结论认为自己需要一个"真正的"向量数据库,这个结论现在已过时了。重新检验它。在这里版本号比 Postgres 世界里几乎任何地方都重要。
反直觉的观点不是"永远不要用专用向量数据库"。而是"知道那条线在哪,在到达之前不要跨过去"。那条线是真实存在的,大致如下:

在约一千万向量以下,经过良好调优且能放进 RAM 的 HNSW 索引的 pgvector 匹配或超越专用引擎,同时你保有了上面所有的免费好处。从大约一千万到五千万,普通 pgvector 开始吃力,但你还不需要离开 Postgres:pgvectorscale 扩展添加了 StreamingDiskANN 索引,在数据集大于 RAM 时仍然保持高速,加上统计二进制量化来缩减内存和标签感知过滤。在一个基准测试中,它在五千万向量、99% 召回率下达成了 471 QPS,大约是同召回率下 Qdrant 吞吐量的 11 倍。所以"你会超越 Postgres"的叙事在它真正成立之前还有整整额外的一章。
超过五千万到一亿向量,或者当你的工作负载是向量搜索优先、有严苛的尾延迟 SLA、且希望别人来操作扩展时,专用引擎真正拉开了差距。Pinecone 是通向数十亿级别零运维扩展的最快路径。Qdrant 在如果你想保持开源并自己运行的情况下赢得原始 QPS 和过滤。Weaviate 捆绑了 embedding 生成,你可以把原始文本交给它。这些是好产品,解决着真实的问题。只是它们解决的问题起始于大多数应用永远不会达到的规模,而且在整个到达那个规模的路上它们向你收取双系统税。
几乎没有人后悔避开的错误是一开始就上 pgvector、然后后续迁移。等你真正撞上墙了,把一亿向量迁移到专用存储是一个已知的、枯燥的数据迁移项目,届时你有 revenue 来配备人员。在第一天就为服务 20 万个向量而架起第二套有状态系统,是你还在没有任何东西值得扩展的时候就把扩展预算花掉的方式。把 embeddings 放进已经持有你数据的那个数据库,加一列 vector,发布功能,等数字——不是演示——告诉你需要的时候再加专用引擎。
Originally published at andriiboyko.com.
If you found this helpful, follow me here and on LinkedIn
For further actions, you may consider blocking this person and/or reporting abuse