向量相似度检索已经成为推荐系统、语义搜索和RAG应用的基础能力。PostgreSQL通过pgvector扩展在关系型数据库内实现了向量存储与检索,而Spotify开源的Annoy则以轻量的近似最近邻算法在工程界广泛使用。两者虽然都解决向量检索问题,但设计取向截然不同,直接对比能帮助开发者避免在选型时走弯路。

核心原理差异:倒排与随机投影森林
PostgreSQL的pgvector扩展提供两类索引:IVFFlat和HNSW。IVFFlat属于倒排文件结构,构建时先对训练向量做K-Means聚类得到若干中心点,每个向量被分配到最近的中心点对应的倒排列表里。查询时只需要计算查询向量与少量中心点的距离,再进入最接近的几个倒排列表做精确距离比较,从而减少计算量。HNSW则构造多层近邻图,查询从上层稀疏图快速靠近目标区域,再在底层稠密图中做贪心搜索。这两种索引的共同特点是依赖数据库中已有的数据分布,并且可以结合SQL条件过滤。
Annoy采用完全不同的思路。它构建一个由多棵随机投影二叉树组成的森林:每次选择一个随机超平面将向量空间一分为二,递归划分直到每个叶子节点包含少量向量。查询时,从每棵树的根节点走到叶子节点,再回溯检查邻近节点,最终汇总所有树的候选结果排序返回。Annoy没有训练阶段,构建过程就是纯粹的递归空间划分,因此构建速度非常快。
从原理上看,PostgreSQL的近似索引更强调与数据库事务和SQL过滤的集成,而Annoy则专注于单机内存中极致的查询效率。前者在数据更新时可以通过索引重建或增量插入维护,后者通常以只读方式加载索引文件,更新成本较高。
索引构建与存储开销对比
使用pgvector的IVFFlat索引时,需要先执行CREATE INDEX ... WITH (lists = 100),其中lists参数决定聚类中心数。构建过程会对训练数据进行K-Means迭代,数据量越大、lists越多,耗时越长。HNSW索引的构建涉及多层图插入,不仅要计算距离,还要维护图连接,因此构建时间通常比IVFFlat更长,但查询性能更好。两者的索引数据都存储在PostgreSQL的表空间中,占用额外的磁盘空间,对于上亿向量可能带来显著的存储放大。
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE items (
id serial PRIMARY KEY,
embedding vector(128)
);
-- IVFFlat 索引
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
-- HNSW 索引
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 200);
Annoy的索引构建相对简单,只需要指定向量维度、距离度量(如angular、euclidean)和树数量。构建过程是递归划分,单线程即可快速完成,且生成的文件可以直接序列化到磁盘。加载索引时使用内存映射,内存占用约为原始向量数据和树结构的总和。与PostgreSQL相比,Annoy不依赖数据库连接,也不需要维护额外的表结构,适合作为独立的检索服务组件。
from annoy import AnnoyIndex
f = 128 # 向量维度
t = AnnoyIndex(f, 'angular')
for i, vector in enumerate(vectors):
t.add_item(i, vector)
t.build(10) # 10 棵树
t.save('annoy_index.ann')
存储方面,Annoy的索引文件紧凑,可以方便地拷贝到多台机器使用,而PostgreSQL的索引位于数据库内部,虽然可以利用复制机制分发,但灵活性不如独立文件。如果需要频繁更新向量,PostgreSQL的索引可以通过重建或删除重新创建,Annoy则缺乏增量更新能力,通常需要定期全量重建。
查询性能与召回率权衡
在查询阶段,pgvector的IVFFlat通过SET ivfflat.probes = 10控制搜索的倒排列表数量。probes越大,召回率越高,但查询变慢。HNSW使用SET hnsw.ef_search = 100控制候选队列大小。一般来说,HNSW在相同召回率下的查询速度优于IVFFlat,但索引构建和内存开销更大。PostgreSQL的查询可以优雅地结合WHERE条件过滤,例如先按类别筛选再在子集内检索向量,这是Annoy难以直接实现的。
SET ivfflat.probes = 20; SELECT id, embedding <-> '[0.1,0.2,...]'::vector AS distance FROM items WHERE category = 'book' ORDER BY embedding <-> '[0.1,0.2,...]'::vector LIMIT 10;
Annoy查询时需要设置搜索节点数search_k,该参数控制回溯检查的节点数量。search_k越大,候选越多,召回率越高,但耗时也增加。Annoy通常返回固定数量的近邻,例如get_nns_by_vector。由于Annoy进程内加载,避免了数据库连接和SQL解析开销,在相同硬件上往往能获得更高的吞吐量,特别适合高并发、低延迟的在线推理场景。
u = AnnoyIndex(f, 'angular')
u.load('annoy_index.ann')
result = u.get_nns_by_vector(query_vector, 10, search_k=200)
从召回率角度看,HNSW通常能用更少的距离计算达到更高召回率,Annoy则需要较多的树和较大的search_k才能接近精确结果。Annoy的优势在于内存占用低和构建快,适用于召回率要求不极端的场景;而PostgreSQL的HNSW更适合希望在数据库内获得较高召回率且能接受一定构建成本的应用。
适用场景与混合架构建议
如果项目已经使用PostgreSQL存储业务数据,并且向量规模在千万级以内,pgvector是降低架构复杂度的首选。开发者可以直接在SQL中完成元数据过滤加向量相似度排序,无需维护额外的向量服务。对于需要事务一致性、数据频繁更新的场景,PostgreSQL的索引维护更加自然,虽然索引重建有一定开销,但可通过分区和异步重建缓解。
Annoy更适用于独立的向量检索服务,例如推荐系统的召回层、图像去重、模型推理后的近邻搜索等。由于Annoy索引文件可以快速加载到内存,并以极高并发响应请求,适合部署在无状态服务中水平扩展。但它不支持增量更新,如果向量集合频繁变化,就需要定期重建,并在重建期间保持双份索引或滚动切换。
实践中,不少团队采用混合架构:PostgreSQL作为主数据源和元数据存储,定期将向量导出构建Annoy索引,Annoy负责在线高并发近似检索,PostgreSQL负责精排或精确查询兜底。这样既保留了关系型数据库的事务能力,又获得了近似检索的性能优势。理解两者的原理和代价,才能根据实际业务做出合理选型。
PostgreSQLAnnoy近似最近邻修改时间:2026-08-25 01:07:34