PGvector 在 Spring AI 中实现向量数据库存储与相似性搜索
本文介绍了 PGvector 作为 PostgreSQL 向量扩展的核心特性及其与 Spring AI 的集成方案。内容包括环境准备、自动与手动配置方式、文档存储与检索操作(含元数据过滤)、高级搜索示例以及性能优化与安全实践。通过合理配置索引类型和距离度量,可实现高效的 RAG 应用开发。

本文介绍了 PGvector 作为 PostgreSQL 向量扩展的核心特性及其与 Spring AI 的集成方案。内容包括环境准备、自动与手动配置方式、文档存储与检索操作(含元数据过滤)、高级搜索示例以及性能优化与安全实践。通过合理配置索引类型和距离度量,可实现高效的 RAG 应用开发。

PGvector 是 PostgreSQL 的开源扩展,专为向量相似性搜索而设计。它允许开发者在 PostgreSQL 数据库中存储和搜索机器学习生成的嵌入(embeddings),支持精确和近似最近邻搜索。
💡 为什么选择 PGvector?
- 无缝集成:作为 PostgreSQL 扩展,与现有数据库生态系统无缝协作
- ACID 合规:保持 PostgreSQL 的事务完整性
- 功能丰富:支持多种距离度量和索引类型
- 高性能:针对大规模向量搜索进行优化
- 易用性:提供标准 SQL 接口,无需学习新查询语言
| 特性 | 描述 | 优势 |
|---|---|---|
| 向量存储 | 支持多种向量类型(vector, halfvec, bit, sparsevec) | 适应不同精度和内存需求 |
| 距离度量 | L2、内积、余弦距离、L1、汉明距离、杰卡德距离 | 适用于不同场景的相似性度量 |
| 索引类型 | HNSW、IVFFlat | 平衡查询速度和召回率 |
| 元数据过滤 | 基于 JSON 的元数据过滤 | 精确控制检索结果 |
| 混合搜索 | 结合向量搜索和文本搜索 | 提高检索相关性 |
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 之前,需要确保 PostgreSQL 实例已启用以下扩展:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
在数据库中创建向量存储表(推荐在应用启动时由 Spring AI 自动创建):
CREATE TABLE IF NOT EXISTS vector_store (
id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
content text,
metadata json,
embedding vector(1536) -- 1536 是默认的嵌入维度
);
CREATE INDEX IF NOT EXISTS vector_index ON vector_store USING hnsw (embedding vector_cosine_ops);
💡 重要提示:如果使用不同的嵌入维度,请将
1536替换为实际的嵌入维度。PGvector 对 HNSW 索引最多支持 2000 个维度。
在 Maven 项目中添加 PGvector 向量存储依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
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)
不使用 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();
}
}
手动配置时,需要添加以下依赖:
<!-- Spring Boot JDBC 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- PostgreSQL JDBC 驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring AI PGvector Store -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store</artifactId>
</dependency>
@Resource
@Qualifier("pgVectorStore")
private VectorStore vectorStore;
/**
* 文档嵌入与存储
*/
public void storeDocuments() {
// 4.1.1 创建文档对象
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(, Map.of(,,,,,))
);
System.out.println();
vectorStore.add(documents);
System.out.println();
List<Document> results = vectorStore.similaritySearch(SearchRequest.builder().query().topK().build());
System.out.println();
results.forEach(doc -> {
System.out.println( + Objects.requireNonNull(doc.getText()).substring(, Math.min(, doc.getText().length())) + );
System.out.println( + doc.getMetadata());
System.out.println();
});
}
/**
* 基本相似性搜索
*/
public List<Document> basicSimilaritySearch() {
System.out.println("\n执行基本相似性搜索...");
SearchRequest searchRequest = SearchRequest.builder()
.query("Spring")
.topK(3)
.similarityThreshold(0.5) // 相似度阈值(0-1 之间)
.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;
}
/**
* 使用元数据过滤 - 文本表达式
*/
public List<Document> searchWithMetadataFilter() {
System.out.println("\n使用元数据过滤搜索...");
SearchRequest searchRequest = SearchRequest.builder()
.query("technology")
.topK(5)
.similarityThreshold(0.3) // 注意:具体过滤表达式语法取决于向量存储实现
// 这里使用 Spring AI 的标准过滤语法
.filterExpression("author in ['john', 'jill'] && article_type == 'blog'")
.build();
// 方法 1: 使用文本表达式语言进行过滤
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;
}
public List<Document> searchWithProgrammaticFilter() {
System.out.println("\n使用编程式元数据过滤搜索...");
// 方法 2: 使用 Filter.Expression DSL 进行编程式过滤
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;
}
public List<Document> advancedSearchExample() {
System.out.println("\n高级搜索示例...");
// 创建复杂过滤条件
FilterExpressionBuilder builder = new FilterExpressionBuilder();
// (author == 'john' || author == 'jill') && category == 'philosophy'
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;
}
public void deleteDocumentsByMetadata() {
System.out.println("\n删除 author 为 'john' 的文档...");
// 注意:删除功能可能因向量存储实现而异
// PGvector 通常支持根据元数据过滤删除
// 先搜索要删除的文档
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()) {
// 提取文档 ID(PGvector 中 id 在 metadata 中)
List<String> ids = docsToDelete.stream()
.map(Document::getId)
.filter(Objects::nonNull)
.toList();
System.out.println("找到 " + ids.size() + " 个要删除的文档");
// 删除文档(具体方法取决于 VectorStore 实现)
vectorStore.delete(ids);
}
}
| 属性 | 描述 | 默认值 | 适用场景 |
|---|---|---|---|
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 | 大批量数据导入 |
| 索引类型 | 构建时间 | 查询性能 | 内存使用 | 适用场景 |
|---|---|---|---|---|
| HNSW | 较慢 | 优秀 | 较高 | 高性能要求,数据量大 |
| IVFFlat | 快 | 一般 | 较低 | 数据量小,内存有限 |
| NONE | 无 | 一般 | 低 | 测试环境,小数据量 |
💡 HNSW 与 IVFFlat 选择建议:
- 对于 100K+ 向量,选择 HNSW
- 对于 10K-100K 向量,选择 IVFFlat
- 对于 <10K 向量,使用 NONE
| 距离类型 | 适用场景 | 性能 | 说明 |
|---|---|---|---|
| COSINE_DISTANCE | 向量已归一化 | 优秀 | 适用于大多数嵌入模型 |
| EUCLIDEAN_DISTANCE | 向量未归一化 | 一般 | 需要精确距离 |
| NEGATIVE_INNER_PRODUCT | 向量已归一化 | 优秀 | 与 COSINE_DISTANCE 等效 |
💡 重要提示:如果向量已经归一化到长度 1(如 OpenAI 嵌入),使用 COSINE_DISTANCE 或 NEGATIVE_INNER_PRODUCT 能获得最佳性能。
// HNSW 索引优化参数
CREATE INDEX ON vector_store USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);
💡 优化建议:对于高召回率要求,提高
ef_construction;对于快速构建,降低它。
// 设置查询时的动态候选列表大小
SET hnsw.ef_search = 100;
💡 优化建议:对于关键查询,设置
ef_search=100;对于普通查询,保持默认。
// 批量添加文档(使用最大批次大小)
vectorStore.add(documents, 10000); // 10000 是 maxDocumentBatchSize
💡 优化建议:使用
maxDocumentBatchSize配置值作为批量大小,避免内存溢出。
-- 为应用用户创建最小权限
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;
// 在应用层验证过滤表达式
if (isValidFilterExpression(filterExpression)) {
// 执行查询
}
💡 安全建议:不要直接使用用户输入的过滤表达式,进行验证和清理。
spring:
ai:
vectorstore:
pgvector:
schema-validation: true
💡 安全建议:在生产环境中始终启用
schema-validation,防止 SQL 注入。
@Bean
public Advisor questionAnswerAdvisor(VectorStore vectorStore) {
return QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder()
.similarityThreshold(0.7)
.topK(5)
.build())
.build();
}
@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();
}
可能原因:
解决方案:
设置查询参数:
SET hnsw.ef_search = 100;
优化索引参数:
CREATE INDEX ON vector_store USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
可能原因:
解决方案:
或在 Spring AI 配置中指定维度:
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) -- 确保与模型一致
);
可能原因:
解决方案:
使用正确的过滤表达式:
.filterExpression("key1 == 'value1' && key2 == 'value2'")
确认元数据格式:
newDocument("内容", Map.of("key1", "value1", "key2", "value2"));
可能原因:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online