PGvector :在 Spring AI 中实现向量数据库存储与相似性搜索

PGvector :在 Spring AI 中实现向量数据库存储与相似性搜索

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 实例已启用以下扩展:

CREATE EXTENSION IFNOTEXISTS vector;CREATE EXTENSION IFNOTEXISTS hstore;CREATE EXTENSION IFNOTEXISTS"uuid-ossp";

如果没有参考 ✅ 宝塔 PostgreSQL 安装 contrib 扩展完整指南

2.2 手动创建向量存储表

在数据库中创建向量存储表(推荐在应用启动时由 Spring AI 自动创建):

CREATETABLEIFNOTEXISTS vector_store ( id uuid DEFAULT uuid_generate_v4()PRIMARYKEY, content text, metadata json, embedding vector(1536)-- 1536 是默认的嵌入维度);CREATEINDEXIFNOTEXISTS 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:1536max-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

@ConfigurationpublicclassPgVectorConfig{@BeanpublicVectorStorevectorStore(JdbcTemplate jdbcTemplate,EmbeddingModel embeddingModel){returnPgVectorStore.builder(jdbcTemplate, embeddingModel).dimensions(1536)// 默认为模型维度.distanceType(COSINE_DISTANCE).indexType(HNSW).initializeSchema(true)// 启用自动表创建.schemaName("public").vectorTableName("vector_store")// 哪一个数据库.maxDocumentBatchSize(10000).build();}}

3.4 依赖项要求

手动配置时,需要添加以下依赖:

<!-- 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>

四、向量存储操作实践

4.1 存储文档

@Resource@Qualifier("pgVectorStore")privateVectorStore vectorStore;/** * 文档嵌入与存储 */publicvoidstoreDocuments(){// 4.1.1 创建文档对象List<Document> documents =List.of(newDocument("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!",Map.of("author","john","article_type","blog","meta1","meta1")),newDocument("The World is Big and Salvation Lurks Around the Corner",Map.of("author","jill","article_type","news","category","philosophy")),newDocument("You walk forward facing the past and you turn back toward the future.",Map.of("author","john","article_type","blog","meta2","meta2","category","philosophy")),newDocument("Spring Framework is a powerful Java framework for building enterprise applications.",Map.of("author","alice","article_type","tutorial","category","programming")),newDocument("Artificial Intelligence is transforming the tech industry.",Map.of("author","bob","article_type","article","category","ai")));// 4.1.2 存储文档到向量存储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 基本相似性搜索

/** * 基本相似性搜索 */publicList<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;}
在这里插入图片描述

4.3 使用元数据过滤 - 文本表达式

/** * 使用元数据过滤 - 文本表达式 */publicList<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;}
在这里插入图片描述

4.4 使用元数据过滤 - Filter.Expression DSL

publicList<Document>searchWithProgrammaticFilter(){System.out.println("\n使用编程式元数据过滤搜索...");// 方法2: 使用 Filter.Expression DSL 进行编程式过滤FilterExpressionBuilder b =newFilterExpressionBuilder();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 高级搜索示例:组合查询和过滤

publicList<Document>advancedSearchExample(){System.out.println("\n高级搜索示例...");// 创建复杂过滤条件FilterExpressionBuilder builder =newFilterExpressionBuilder();// (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;}
在这里插入图片描述

4.6 删除特定文档(根据元数据)

在这里插入图片描述
publicvoiddeleteDocumentsByMetadata(){System.out.println("\n删除 author 为 'john' 的文档...");// 注意:删除功能可能因向量存储实现而异// PGvector 通常支持根据元数据过滤删除// 先搜索要删除的文档FilterExpressionBuilder builder =newFilterExpressionBuilder();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);}}

五、PGvector 配置参数详解

5.1 关键配置属性

属性描述默认值适用场景
index-type最近邻搜索索引类型HNSW需要高性能检索时
distance-type搜索距离类型COSINE_DISTANCE向量已归一化时
dimensions嵌入维度从 EmbeddingModel 获取与嵌入模型一致
remove-existing-vector-store-table启动时删除现有表false重置数据库时
initialize-schema是否初始化 schemafalse首次使用时
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);// 10000 是 maxDocumentBatchSize
💡 优化建议:使用 maxDocumentBatchSize 配置值作为批量大小,避免内存溢出。

6.2 安全最佳实践

6.2.1 数据库权限管理
-- 为应用用户创建最小权限CREATE ROLE app_user;GRANTCONNECTONDATABASE postgres TO app_user;GRANTUSAGEONSCHEMApublicTO app_user;GRANTSELECT,INSERT,UPDATE,DELETEONTABLE 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
@BeanpublicAdvisorquestionAnswerAdvisor(VectorStore vectorStore){returnQuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().similarityThreshold(0.7).topK(5).build()).build();}
6.3.2 使用 RetrievalAugmentationAdvisor
@BeanpublicAdvisorretrievalAugmentationAdvisor(VectorStore vectorStore){returnRetrievalAugmentationAdvisor.builder().documentRetriever(VectorStoreDocumentRetriever.builder().vectorStore(vectorStore).similarityThreshold(0.7).topK(5).build()).queryAugmenter(ContextualQueryAugmenter.builder().allowEmptyContext(true).build()).build();}
6.3.3 RAG 流程工作流程
  1. 用户输入:用户提出问题
  2. 查询处理:使用 QueryTransformer 优化查询
  3. 文档检索:使用 VectorStoreDocumentRetriever 检索相关文档
  4. 上下文增强:使用 ContextualQueryAugmenter 增强查询
  5. 生成响应:使用 ChatModel 生成最终响应

七、常见问题与解决方案

7.1 问题:查询性能差

可能原因

  • 没有使用合适的索引
  • 索引参数未优化

解决方案

  1. 确保已创建 HNSW 或 IVFFlat 索引

设置查询参数:

SET hnsw.ef_search =100;

优化索引参数:

CREATEINDEXON vector_store USING hnsw (embedding vector_cosine_ops)WITH(m =16, ef_construction =64);

7.2 问题:向量维度不匹配

可能原因

  • 数据库表中的嵌入维度与 EmbeddingModel 不一致

解决方案

  1. 确认 EmbeddingModel 的维度

或在 Spring AI 配置中指定维度:

spring:ai:vectorstore:pgvector:dimensions:1536

在数据库表中设置正确的维度:

CREATETABLE vector_store ( id uuid DEFAULT uuid_generate_v4()PRIMARYKEY, content text, metadata json, embedding vector(1536)-- 确保与模型一致);

7.3 问题:元数据过滤无效

可能原因

  • 过滤表达式格式不正确
  • 元数据存储格式不一致

解决方案

  1. 检查 PostgreSQL 日志,查看实际执行的 SQL 语句

使用正确的过滤表达式:

.filterExpression("key1 == 'value1' && key2 == 'value2'")

确认元数据格式:

newDocument("内容",Map.of("key1","value1","key2","value2"));

7.4 问题:内存不足

可能原因

  • HNSW 索引构建时内存不足
  • 查询时内存不足

八、总结

关键要点回顾

  • PGvector :是 PostgreSQL 的开源向量扩展,支持高效向量相似性搜索
  • Spring AI :通过 spring-ai-starter-vector-store-pgvector 提供了开箱即用的集成
  • 向量存储 :需要正确配置维度、距离类型和索引类型
  • 元数据过滤 :提供了强大的结果筛选能力
  • 性能优化 :通过调整索引和查询参数实现
Could not load content