向量数据库学习笔记(3) —— pgvector 最佳实践
注意:不要将maintenance_work_mem设置过高,以免耗尽服务器内存。:pgvector 通过预写日志(WAL)实现,兼容主从复制和时间点恢复(PITR)。朋友越多,找人时越容易通过朋友链找到目标,但维护朋友关系也更费内存和时间。值越大,图的连通性越强,搜索路径更多,召回率(Recall)更高。值越大,搜索时考察的潜在相似向量越多,召回率越高,但查询速度越慢。值越大,构建的图质量越高,
一、 pgvector与toast设置

二、 HNSW索引
1. 索引选项
指定 HNSW 索引的参数:
-
m- 控制 HNSW 图索引中每个节点(向量)在每一层的最大连接数。(默认为16)
作用:
值越大,图的连通性越强,搜索路径更多,召回率(Recall)更高。
但会增加索引的内存占用和构建时间。
想象 HNSW 是一张多层的社交网络:
m=16表示每个人(向量)最多和 16 个朋友直接联系。朋友越多,找人时越容易通过朋友链找到目标,但维护朋友关系也更费内存和时间。
-
ef_construction- 在构建索引时,动态保留的候选向量数量(默认为64)
作用:
值越大,构建的图质量越高,搜索精度更高,但索引构建速度越慢。
影响索引的“粗调”阶段,决定如何连接节点。
假设你要建一个城市地图(HNSW 索引):
ef_construction=64表示画地图时,每次会考虑 64 个可能的路口来规划最优路线。考虑的路口越多,地图越精确,但画地图的时间也越长。


使用方法
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
2. 查询选项
ef_search:指定搜索时的动态候选列表大小(默认为40)
作用:
值越大,搜索时考察的潜在相似向量越多,召回率越高,但查询速度越慢。
仅影响查询阶段,与索引构建无关。
想象你在图书馆(HNSW 索引)找一本书:
ef_search=40表示你会先查看 40 本可能相关的书,再挑出最匹配的。检查的书越多(
ef_search越大),找到理想书的概率越高,但耗时也更长。
使用方法
SET hnsw.ef_search = 100;
如需在单个查询中临时设置,可在事务内使用 SET LOCAL
BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT ...
COMMIT;
3. 加速索引构建
① 内存
如果图结构能完全放入 maintenance_work_mem,索引构建速度会显著提升:
SET maintenance_work_mem = '8GB';
如果内存不足,会显示提示:
NOTICE: HNSW 图结构在 100000 条元组后无法完全放入 maintenance_work_mem DETAIL: 构建时间将显著增加。
HINT: 增加 maintenance_work_mem 可加速构建。
注意:不要将maintenance_work_mem设置过高,以免耗尽服务器内存。与其他索引类型一样,初始数据加载完成后再创建索引会更快。
② 并行
还可通过增加并行工作线程数(默认为2)加速索引创建:
SET max_parallel_maintenance_workers = 7; -- 包含主线程
若工作线程数较多,可能需要调整 max_parallel_workers(默认为 8)。
③ 索引参数
如 m 和 ef_construction,也会显著影响构建时间(若无召回率问题,建议使用默认值)。
4. 索引构建进度
查看进度:
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%"
FROM pg_stat_progress_create_index;
HNSW 索引的构建阶段(phase)包括:
- initializing(初始化)
- loading tuples(加载数据元组)
三、 IVFFlat索引
1. 查询选项
probes:控制搜索时需要检查的聚类数量(默认为 1)

作用:
- 值越大,召回率(Recall)越高,但查询速度越慢
- 若设置为聚类数(
lists),则退化为精确最近邻搜索(此时规划器将不再使用索引)类比:
将 IVFFlat 索引比作一个图书馆:
聚类(
lists):图书馆的书按主题分到不同区域(如“科幻区”“历史区”)
probes:你要搜索“科幻小说”时,决定检查几个主题区参数影响:
probes=1:只检查最相关的 1 个区(如“科幻区”),最快但可能漏掉其他区的书
probes=10:检查 10 个相关区(如“科幻区”“奇幻区”等),找到更多好书但速度变慢
probes=所有区:相当于全馆搜索,结果最全但和最慢使用建议:
精度优先:增大
probes(如10)速度优先:减小
probes(如1~3)
SET ivfflat.probes = 10;
单次查询临时设置(使用事务内 SET LOCAL):
BEGIN;
SET LOCAL ivfflat.probes = 10;
SELECT ...;
COMMIT;
2. 加速索引构建
可通过增加并行工作线程数(默认为2)加速索引创建:
SET max_parallel_maintenance_workers = 7; -- 包含主线程
若工作线程数较多,可能需要调整 max_parallel_workers(默认为 8)。
3. 索引构建进度
查看进度
SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%"
FROM pg_stat_progress_create_index;
IVFFlat 构建阶段:
-
initializing(初始化) -
performing k-means(执行 K-Means 聚类) -
assigning tuples(分配元组) -
loading tuples(加载元组),百分比(%)仅在loading tuples阶段显示。
四、 迭代式索引扫描(Iterative Index Scans)
自动扩展索引扫描范围,直至找到足够结果或达到 max_scan_tuples/max_probes 限制。
1. 模式选择
- 严格排序(Strict Order):确保结果按距离精确排序
SET hnsw.iterative_scan = strict_order;
- 宽松排序(Relaxed Order):允许轻微顺序偏差以提升召回率
SET hnsw.iterative_scan = relaxed_order;
-- 或
SET ivfflat.iterative_scan = relaxed_order;
2. 迭代扫描控制参数
HNSW
-
hnsw.max_scan_tuples:最大扫描元组数 (默认为
20000): -
hnsw.scan_mem_multiplier:内存使用倍数(基于
work_mem,默认为1):提示:若增大
max_scan_tuples未提升召回率,可尝试调整此参数。
IVFFlat
-
ivfflat.max_probes:最大探测数:
注意:若此值小于
ivfflat.probes,则以probes为准。
五、Q&A
1. 单表最多能存储多少向量?
-
默认限制:PostgreSQL 非分区表默认支持 32 TB 数据,分区表可扩展至数千个同等大小的分区。
2. 是否支持数据复制?
-
支持:pgvector 通过预写日志(WAL)实现,兼容主从复制和时间点恢复(PITR)。
3. 如何索引超过 2,000 维的向量?
-
半精度索引(half-precision):支持最高 4,000 维。
- 二进制量化(binary quantization):支持最高 64,000 维。
- 降维处理:如 PCA 降低维度后再索引。
4. 同一列能否存储不同维度的向量?
存储:可使用无维度声明的 vector 类型:
CREATE TABLE embeddings (
model_id bigint,
item_id bigint,
embedding vector,
PRIMARY KEY (model_id, item_id)
);
索引限制:需对固定维度的子集创建索引(通过表达式索引+条件过滤):
CREATE INDEX ON embeddings USING hnsw ((embedding::vector(3)) vector_l2_ops) WHERE (model_id = 123);
查询示例:
SELECT * FROM embeddings
WHERE model_id = 123
ORDER BY embedding::vector(3) <-> '[3,1,2]'
LIMIT 5;
5. 如何存储更高精度的向量?
-
方法:使用
double precision[]或numeric[]类型:CREATE TABLE items ( id bigserial PRIMARY KEY, embedding double precision[] ); -- 插入数据(Postgres 数组用 {}) INSERT INTO items (embedding) VALUES ('{1,2,3}'), ('{4,5,6}'); -
可选约束:确保数据可转换为向量且维度正确:
ALTER TABLE items ADD CHECK (vector_dims(embedding::vector) = 3);
-
表达式索引:以较低精度创建索引:
CREATE INDEX ON items USING hnsw ((embedding::vector(3)) vector_l2_ops);
-
查询示例:
SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
6. 索引是否需要全部装入内存?
-
否:但与其他索引类型一样,内存足够时性能更优。
-
查看索引大小:
SELECT pg_size_pretty(pg_relation_size('index_name'));
故障排查
1. 查询为何未使用索引?
-
必要条件:
-
查询需包含
ORDER BY+LIMIT,且排序需直接使用距离运算符(升序):-- 使用索引 ORDER BY embedding <=> '[3,1,2]' LIMIT 5; -- 不使用索引(表达式修饰) ORDER BY 1 - (embedding <=> '[3,1,2]') DESC LIMIT 5;
-
-
强制使用索引:
BEGIN; SET LOCAL enable_seqscan = off; SELECT ...; COMMIT;
-
小表提示:数据量少时,全表扫描可能更快。
2. 查询为何未启用并行扫描?
-
原因:规划器可能低估了并行扫描成本。
-
解决方案:
BEGIN; SET LOCAL min_parallel_table_scan_size = 1; SET LOCAL parallel_setup_cost = 1; SELECT ...; COMMIT;
-
或改为行内存储:
ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;
3. 添加 HNSW 索引后结果变少?
-
动态候选列表限制:默认
hnsw.ef_search=40可能限制结果数量。 -
其他原因:
-
死元组(dead tuples)或查询中的过滤条件。
-
NULL 向量和零向量(余弦距离时)不会被索引。
-
-
解决方案:启用迭代式索引扫描或增大
ef_search。
4. 添加 IVFFlat 索引后结果变少?
-
常见原因:
-
创建索引时数据量不足(相对于
lists参数)。 -
探测数
ivfflat.probes设置过低。
-
-
解决方案:
-
数据量不足时删除索引,待数据增长后重建:
DROP INDEX index_name;
-
启用迭代式扫描或增大
probes。
-
-
注意:NULL 向量和零向量(余弦距离时)不会被索引。
六、 pgvector后续计划

参考:
部分内容来自AI回答
https://github.com/pgvector/pgvector?tab=readme-ov-file#hnsw
更多推荐




所有评论(0)