跳到主要内容 PGvector 在 Spring AI 中实现向量数据库存储与相似性搜索 | 极客日志
Java AI java 算法
PGvector 在 Spring AI 中实现向量数据库存储与相似性搜索 PGvector 作为 PostgreSQL 向量扩展的核心特性及其与 Spring AI 的集成方案。内容包括环境准备、自动与手动配置方式、文档存储与检索操作(含元数据过滤)、高级搜索示例以及性能优化与安全实践。通过合理配置索引类型和距离度量,可实现高效的 RAG 应用开发。
BigDataPan 发布于 2026/3/29 更新于 2026/4/18 9 浏览PGvector 在 Spring AI 中实现向量数据库存储与相似性搜索
一、PGvector 概述与核心价值
1.1 什么是 PGvector
PGvector 是 PostgreSQL 的开源扩展,专为向量相似性搜索而设计。它允许开发者在 PostgreSQL 数据库中存储和搜索机器学习生成的嵌入(embeddings),支持精确和近似最近邻搜索。
💡 为什么选择 PGvector?
无缝集成 :作为 PostgreSQL 扩展,与现有数据库生态系统无缝协作
ACID 合规 :保持 PostgreSQL 的事务完整性
功能丰富 :支持多种距离度量和索引类型
高性能 :针对大规模向量搜索进行优化
易用性 :提供标准 SQL 接口,无需学习新查询语言
1.2 PGvector 的关键特性
特性 描述 优势 向量存储 支持多种向量类型(vector, halfvec, bit, sparsevec) 适应不同精度和内存需求 距离度量 L2、内积、余弦距离、L1、汉明距离、杰卡德距离 适用于不同场景的相似性度量 索引类型 HNSW、IVFFlat 平衡查询速度和召回率 元数据过滤 基于 JSON 的元数据过滤 精确控制检索结果 混合搜索 结合向量搜索和文本搜索 提高检索相关性
1.3 PGvector 与 Spring AI 的集成
Spring AI 通过 spring-ai-starter-vector-store-pgvector 提供了 PGvector 的开箱即用支持,使开发者能够轻松地将向量数据库集成到 RAG(检索增强生成)应用中。
<dependency >
<groupId > org.springframework.ai</groupId >
<artifactId > spring-ai-starter-vector-store-pgvector</artifactId >
</dependency >
二、PGvector 安装与配置
2.1 环境准备
2.1.1 前提条件
在使用 PGvector 之前,需要确保 PostgreSQL 实例已启用以下扩展:
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
2.2 手动创建向量存储表 在数据库中创建向量存储表(推荐在应用启动时由 Spring AI 自动创建):
CREATE TABLE IF NOT EXISTS vector_store (
id uuid DEFAULT uuid_generate_v4() PRIMARY KEY ,
content text,
metadata json,
embedding vector(1536 )
);
CREATE INDEX IF NOT EXISTS vector_index ON vector_store USING hnsw (embedding vector_cosine_ops);
💡 重要提示 :如果使用不同的嵌入维度,请将 1536 替换为实际的嵌入维度。PGvector 对 HNSW 索引最多支持 2000 个维度。
三、Spring AI 集成 PGvector
3.1 依赖配置 在 Maven 项目中添加 PGvector 向量存储依赖:
<dependency >
<groupId > org.springframework.ai</groupId >
<artifactId > spring-ai-starter-vector-store-pgvector</artifactId >
</dependency >
3.2 自动配置 Spring AI 提供了自动配置功能,通过 application.yml 配置 PGvector:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: postgres
ai:
vectorstore:
pgvector:
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 1536
max-document-batch-size: 10000
💡 关键配置说明 :
initialize-schema: true:在应用启动时自动创建所需的数据库表
dimensions:嵌入维度(如果未指定,将从 EmbeddingModel 获取)
index-type:索引类型(HNSW、IVFFlat、NONE)
distance-type:距离类型(COSINE_DISTANCE、EUCLIDEAN_DISTANCE、NEGATIVE_INNER_PRODUCT)
3.3 手动配置(高级用法) 不使用 Spring Boot 自动配置时,可以手动创建 PgVectorStore:
@Configuration
public class PgVectorConfig {
@Bean
public VectorStore vectorStore (JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel) {
return PgVectorStore.builder(jdbcTemplate, embeddingModel)
.dimensions(1536 )
.distanceType(COSINE_DISTANCE)
.indexType(HNSW)
.initializeSchema(true )
.schemaName("public" )
.vectorTableName("vector_store" )
.maxDocumentBatchSize(10000 )
.build();
}
}
3.4 依赖项要求
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-jdbc</artifactId >
</dependency >
<dependency >
<groupId > org.postgresql</groupId >
<artifactId > postgresql</artifactId >
<scope > runtime</scope >
</dependency >
<dependency >
<groupId > org.springframework.ai</groupId >
<artifactId > spring-ai-pgvector-store</artifactId >
</dependency >
四、向量存储操作实践
4.1 存储文档 @Resource
@Qualifier("pgVectorStore")
private VectorStore vectorStore;
public void storeDocuments () {
List<Document> documents = List.of(
new Document ("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!" , Map.of("author" ,"john" ,"article_type" ,"blog" ,"meta1" ,"meta1" )),
new Document ("The World is Big and Salvation Lurks Around the Corner" , Map.of("author" ,"jill" ,"article_type" ,"news" ,"category" ,"philosophy" )),
new Document ("You walk forward facing the past and you turn back toward the future." , Map.of("author" ,"john" ,"article_type" ,"blog" ,"meta2" ,"meta2" ,"category" ,"philosophy" )),
new Document ("Spring Framework is a powerful Java framework for building enterprise applications." , Map.of("author" ,"alice" ,"article_type" ,"tutorial" ,"category" ,"programming" )),
new Document ("Artificial Intelligence is transforming the tech industry." , Map.of("author" ,"bob" ,"article_type" ,"article" ,"category" ,"ai" ))
);
System.out.println("开始存储文档到向量存储..." );
vectorStore.add(documents);
System.out.println("文档存储完成!" );
List<Document> results = vectorStore.similaritySearch(SearchRequest.builder().query("Spring" ).topK(5 ).build());
System.out.println("\n存储验证 - 检索结果:" );
results.forEach(doc -> {
System.out.println("内容:" + Objects.requireNonNull(doc.getText()).substring(0 , Math.min(50 , doc.getText().length())) + "..." );
System.out.println("元数据:" + doc.getMetadata());
System.out.println("---" );
});
}
4.2 基本相似性搜索
public List<Document> basicSimilaritySearch () {
System.out.println("\n执行基本相似性搜索..." );
SearchRequest searchRequest = SearchRequest.builder()
.query("Spring" )
.topK(3 )
.similarityThreshold(0.5 )
.build();
List<Document> results = vectorStore.similaritySearch(searchRequest);
System.out.println("基本搜索 - 查询 'Spring' 的结果:" );
results.forEach(doc -> {
System.out.println("内容:" + doc.getText());
System.out.println("元数据:" + doc.getMetadata());
System.out.println("相似度:" + doc.getMetadata().get("distance" ));
System.out.println("---" );
});
return results;
}
4.3 使用元数据过滤 - 文本表达式
public List<Document> searchWithMetadataFilter () {
System.out.println("\n使用元数据过滤搜索..." );
SearchRequest searchRequest = SearchRequest.builder()
.query("technology" )
.topK(5 )
.similarityThreshold(0.3 )
.filterExpression("author in ['john', 'jill'] && article_type == 'blog'" )
.build();
List<Document> results = vectorStore.similaritySearch(searchRequest);
System.out.println("元数据过滤搜索 - 文本表达式结果:" );
results.forEach(doc -> {
System.out.println("内容:" + doc.getText());
System.out.println("元数据:" + doc.getMetadata());
System.out.println("---" );
});
return results;
}
4.4 使用元数据过滤 - Filter.Expression DSL public List<Document> searchWithProgrammaticFilter () {
System.out.println("\n使用编程式元数据过滤搜索..." );
FilterExpressionBuilder b = new FilterExpressionBuilder ();
List<Document> results = vectorStore.similaritySearch(
SearchRequest.builder()
.query("technology" )
.topK(5 )
.similarityThreshold(0.3 )
.filterExpression(b.and(
b.in("author" , "john" , "jill" ),
b.eq("article_type" , "blog" )
).build())
.build()
);
System.out.println("编程式过滤搜索结果:" );
results.forEach(doc -> {
System.out.println("内容:" + doc.getText());
System.out.println("元数据:" + doc.getMetadata());
System.out.println("---" );
});
return results;
}
4.5 高级搜索示例:组合查询和过滤 public List<Document> advancedSearchExample () {
System.out.println("\n高级搜索示例..." );
FilterExpressionBuilder builder = new FilterExpressionBuilder ();
Filter.Expression filter = builder.and(
builder.or(
builder.eq("author" , "john" ),
builder.eq("author" , "jill" )
),
builder.eq("category" , "philosophy" )
).build();
List<Document> results = vectorStore.similaritySearch(
SearchRequest.builder()
.query("life and future" )
.topK(10 )
.similarityThreshold(0.0 )
.filterExpression(filter)
.build()
);
System.out.println("高级搜索结果:" );
results.forEach(doc -> {
System.out.println("内容:" + doc.getText());
System.out.println("元数据:" + doc.getMetadata());
System.out.println("---" );
});
return results;
}
4.6 删除特定文档(根据元数据) public void deleteDocumentsByMetadata () {
System.out.println("\n删除 author 为 'john' 的文档..." );
FilterExpressionBuilder builder = new FilterExpressionBuilder ();
Filter.Expression filter = builder.eq("author" , "john" ).build();
List<Document> docsToDelete = vectorStore.similaritySearch(
SearchRequest.builder()
.query("" )
.topK(100 )
.filterExpression(filter)
.build()
);
if (!docsToDelete.isEmpty()) {
List<String> ids = docsToDelete.stream()
.map(Document::getId)
.filter(Objects::nonNull)
.toList();
System.out.println("找到 " + ids.size() + " 个要删除的文档" );
vectorStore.delete(ids);
}
}
五、PGvector 配置参数详解
5.1 关键配置属性 属性 描述 默认值 适用场景 index-type最近邻搜索索引类型 HNSW 需要高性能检索时 distance-type搜索距离类型 COSINE_DISTANCE 向量已归一化时 dimensions嵌入维度 从 EmbeddingModel 获取 与嵌入模型一致 remove-existing-vector-store-table启动时删除现有表 false 重置数据库时 initialize-schema是否初始化 schema false 首次使用时 schema-name向量存储 schema 名称 public 多 schema 环境 table-name向量存储表名称 vector_store 自定义表名 schema-validation启用 schema 和表名验证 false 安全敏感环境 max-document-batch-size单批处理的最大文档数 10000 大批量数据导入
5.2 索引类型对比 索引类型 构建时间 查询性能 内存使用 适用场景 HNSW 较慢 优秀 较高 高性能要求,数据量大 IVFFlat 快 一般 较低 数据量小,内存有限 NONE 无 一般 低 测试环境,小数据量
💡 HNSW 与 IVFFlat 选择建议 :
对于 100K+ 向量,选择 HNSW
对于 10K-100K 向量,选择 IVFFlat
对于 <10K 向量,使用 NONE
5.3 距离类型选择 距离类型 适用场景 性能 说明 COSINE_DISTANCE 向量已归一化 优秀 适用于大多数嵌入模型 EUCLIDEAN_DISTANCE 向量未归一化 一般 需要精确距离 NEGATIVE_INNER_PRODUCT 向量已归一化 优秀 与 COSINE_DISTANCE 等效
💡 重要提示 :如果向量已经归一化到长度 1(如 OpenAI 嵌入),使用 COSINE_DISTANCE 或 NEGATIVE_INNER_PRODUCT 能获得最佳性能。
六、高级用法与最佳实践
6.1 性能优化技巧
6.1.1 索引优化 / / HNSW 索引优化参数
CREATE INDEX ON vector_store USING hnsw (embedding vector_cosine_ops) WITH (m = 16 , ef_construction = 64 );
m :每层的最大连接数(默认 16)
ef_construction :构建图时的动态候选列表大小(默认 64)
💡 优化建议 :对于高召回率要求,提高 ef_construction;对于快速构建,降低它。
6.1.2 查询优化 / / 设置查询时的动态候选列表大小
SET hnsw.ef_search = 100 ;
ef_search :查询时的动态候选列表大小(默认 40)
值越大,召回率越高,但速度越慢
💡 优化建议 :对于关键查询,设置 ef_search=100;对于普通查询,保持默认。
6.1.3 批量处理优化
vectorStore.add(documents, 10000 );
💡 优化建议 :使用 maxDocumentBatchSize 配置值作为批量大小,避免内存溢出。
6.2 安全最佳实践
6.2.1 数据库权限管理
CREATE ROLE app_user;
GRANT CONNECT ON DATABASE postgres TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT , INSERT , UPDATE , DELETE ON TABLE vector_store TO app_user;
6.2.2 元数据过滤安全
if (isValidFilterExpression(filterExpression)) {
}
💡 安全建议 :不要直接使用用户输入的过滤表达式,进行验证和清理。
6.2.3 启用 schema 验证 spring:
ai:
vectorstore:
pgvector:
schema-validation: true
💡 安全建议 :在生产环境中始终启用 schema-validation,防止 SQL 注入。
6.3 与 RAG 流程集成
6.3.1 使用 QuestionAnswerAdvisor @Bean
public Advisor questionAnswerAdvisor (VectorStore vectorStore) {
return QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder()
.similarityThreshold(0.7 )
.topK(5 )
.build())
.build();
}
6.3.2 使用 RetrievalAugmentationAdvisor @Bean
public Advisor retrievalAugmentationAdvisor (VectorStore vectorStore) {
return RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.7 )
.topK(5 )
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true )
.build())
.build();
}
6.3.3 RAG 流程工作流程
用户输入 :用户提出问题
查询处理 :使用 QueryTransformer 优化查询
文档检索 :使用 VectorStoreDocumentRetriever 检索相关文档
上下文增强 :使用 ContextualQueryAugmenter 增强查询
生成响应 :使用 ChatModel 生成最终响应
七、常见问题与解决方案
7.1 问题:查询性能差 SET hnsw.ef_search = 100 ;
CREATE INDEX ON vector_store USING hnsw (embedding vector_cosine_ops)
WITH (m = 16 , ef_construction = 64 );
7.2 问题:向量维度不匹配
数据库表中的嵌入维度与 EmbeddingModel 不一致
spring:
ai:
vectorstore:
pgvector:
dimensions: 1536
CREATE TABLE vector_store (
id uuid DEFAULT uuid_generate_v4() PRIMARY KEY ,
content text,
metadata json,
embedding vector(1536 )
);
7.3 问题:元数据过滤无效
检查 PostgreSQL 日志,查看实际执行的 SQL 语句
.filterExpression("key1 == 'value1' && key2 == 'value2'" )
newDocument("内容" , Map.of("key1" , "value1" , "key2" , "value2" ));
7.4 问题:内存不足