Sparkle CodesSparkle
项目 / 数据库

pgvector 向量索引

x
xpx
Dec 23, 2024
Editorial Insight
#pgvector#PostgreSQL#RAG#向量数据库#向量索引

pgvector 向量索引

pgvector 不是 PostgreSQL 内核自带,而是一个扩展。它把向量作为 PostgreSQL 里的数据类型来存,并提供向量距离运算与近邻索引。当前公开文档显示它支持 vector、halfvec、bit、sparsevec 等类型,以及 L2、内积、余弦、L1 等距离/相似度相关操作。

pgvector 是怎么接入 PostgreSQL 的

从 PostgreSQL 体系结构上看,pgvector 做的事情,本质上和 PostGIS 很像:不是改造 heap,而是通过扩展数据类型 + operator class + index access method 接入索引机制,把"向量相似搜索"接进 planner/executor。PostgreSQL 的索引类型体系本来就是可扩展的,GiST/GIN 等官方文档也明确强调了这种 extensibility。

pgvector 的索引数据结构

pgvector 最常用的是两种近似最近邻(ANN)索引:

HNSW

HNSW 是 Hierarchical Navigable Small World 图索引。它不是 B-tree,也不是 GiST/GIN/BRIN 这种 PostgreSQL 内置 access method 之一,而是面向向量 ANN 的图结构:高层是稀疏导航图,低层更密,用"从高层快速逼近、再到底层精细搜索"的方式找近邻。pgvector 文档里给出了 HNSW 的构建参数、查询参数(如 hnsw.ef_search)以及 0.8.0 起支持 iterative index scans。

IVFFlat

IVFFlat 是 倒排文件 + 聚类列表(lists) 思路。它会先把向量空间切成很多 lists,查询时只探测离查询向量最近的若干 lists。官方 README 说明它通常比 HNSW 构建更快、占内存更少,但在 speed-recall tradeoff 上通常不如 HNSW。

所以,pgvector 的"索引数据结构"不是树,而主要是 ANN 图结构(HNSW)或聚类倒排列表结构(IVFFlat)。这和你前面问的 PostgreSQL 传统索引很不同:

  • B-tree/GiST 追求精确可判定的搜索路径
  • pgvector 更强调 近似搜索下的 recall / latency / memory 平衡

怎么理解 pgvector 的性能优化原理

普通索引优化的是"少扫行、少回表";而 pgvector 优化的是"不要和所有向量逐个算距离"。

如果没有向量索引,最近邻查询通常是:

SQL
ORDER BY embedding <-> query_vector
LIMIT k

本质接近"全表算距离再取 Top-K"。有了 HNSW / IVFFlat 后,数据库不再对全表所有向量做精确距离计算,而是:

  • HNSW:在图上沿近邻边逐步逼近候选区
  • IVFFlat:只在最相关的若干 list 里算距离

因此收益不是来自 O(log n) 这种传统树复杂度,而是来自 大幅减少需要参与精确距离计算的候选向量数。同时,这类索引往往是 近似 的:更快,但可能牺牲部分召回率;可以通过 ef_search、probes 之类参数换 recall。

这也是为什么向量索引和 B-tree 不能互相替代:

  • B-tree 擅长"有序标量"
  • pgvector 索引擅长"高维相似度空间"

pgvector 的查询参数调优

HNSW 参数

hnsw.ef_search

设置 HNSW 搜索的动态候选列表大小(默认 40)。更高的值会增加召回率,但也会增加查询延迟。

SQL
SET hnsw.ef_search = 100;

hnsw.iterative_scan(0.8.0+)

启用迭代扫描以支持过滤查询。可以设置为:

  • strict_order:按距离精确排序
  • relaxed_order:轻微的顺序偏差以获得更好的召回率
SQL
SET hnsw.iterative_scan = strict_order;
-- 或
SET hnsw.iterative_scan = relaxed_order;

hnsw.max_scan_tuples

设置迭代 HNSW 扫描的最大元组数。

SQL
SET hnsw.max_scan_tuples = 20000;

IVFFlat 参数

ivfflat.probes

设置搜索期间探测的列表数量(默认 1)。增加 probes 可以提高召回率,但会降低查询速度。将其设置为列表总数可以确保精确的最近邻搜索。

SQL
SET ivfflat.probes = 10;

ivfflat.iterative_scan(0.8.0+)

启用 IVFFlat 的迭代扫描。

SQL
SET ivfflat.iterative_scan = relaxed_order;

ivfflat.max_probes

设置迭代 IVFFlat 扫描的最大 probes 数。

SQL
SET ivfflat.max_probes = 100;

事务级参数设置

可以使用 SET LOCAL 在单个事务中设置参数:

SQL
BEGIN;
SET LOCAL hnsw.ef_search = 200;
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
COMMIT;

pgvector 在 PostgreSQL 里的一个关键现实:过滤条件和 ANN 的配合

pgvector 文档特别提到,从 0.8.0 起支持 iterative index scans,会在过滤条件导致结果数不够时自动继续多扫一些索引区域。还建议在离散过滤值较少时考虑 partial indexing 或分区。

这说明一个很重要的工程事实:

向量搜索在数据库里通常不是"纯向量",而是"结构化过滤 + ANN 检索"混合查询。

例如:

SQL
WHERE tenant_id = 42
  AND created_at >= now() - interval '30 days'
ORDER BY embedding <=> :query
LIMIT 20

此时最好的设计通常不是"只建一个 vector index",而是把:

  • 租户隔离、时间过滤交给普通 PostgreSQL 机制
  • 向量相似排序交给 pgvector 索引

联合考虑。

pgvector 的使用场景

相似搜索 / RAG / Embedding 检索

选 pgvector

  • 几十万到上亿向量:考虑 HNSW / IVFFlat
  • 需要结构化过滤:把租户、状态、时间等条件一起设计

HNSW vs IVFFlat 选择

HNSW

  • 优点:更高的召回率和查询性能
  • 缺点:构建时间更长,内存占用更大
  • 适用场景:需要高召回率的实时查询

IVFFlat

  • 优点:构建更快,内存占用更少
  • 缺点:召回率和查询性能通常不如 HNSW
  • 适用场景:离线批量构建,内存受限环境

pgvector 的性能考虑

索引构建

  • HNSW 构建时间较长,适合离线批量构建
  • IVFFlat 构建更快,适合频繁重建的场景
  • NULL 和零向量不会被索引

查询性能

  • 调整 ef_search 和 probes 可以在速度和召回率之间权衡
  • 对于过滤查询,使用 iterative scan 可以提高召回率
  • 考虑使用 partial index 或分区来优化混合查询

维护成本

  • 向量索引通常比传统索引更大
  • 需要定期监控索引大小和查询性能
  • 考虑向量维度和索引类型的权衡

下一步

理解了 pgvector 向量索引后,可以继续阅读 PostgreSQL 索引体系总览 了解其他索引类型,或阅读 PostGIS 空间索引 了解地理信息的索引实现。

BACK TO BLOG
The End of Interaction