内存/Redis/PGVector/Milvus/ES 怎么选?Spring AI VectorStore 抽象与向量库选型对照表
系列:Spring AI 深入浅出(第 10 篇)
版本:Spring AI 2.0.1 / Spring Boot 4.1.x / JDK 21
标签:#SpringAI #向量库 #PGVector #RAG
前置阅读:第 9 篇《RAG 原理与 ETL 管线》
预计字数:6500 字|可复制源码 + 选型对照表 + 踩坑 FAQ
本文将解决 :VectorStore 一个抽象换掉,RAG 代码不用改;难的是选对库。本文给你一张照着选的对照表,并用 PGVector 跑通接入。
你将学到:
VectorStore可移植抽象(2.0 统一,覆盖 20+ 数据库)- SQL-like 元数据过滤(
FilterExpression) - 各向量库选型对照表(内存/PGVector/Redis/Milvus/ES)
- embedding 模型选择与维度一致性
- PGVector 完整接入实战
一、版本标注
本文基于 Spring AI 2.0.1 / Boot 4.1 / JDK 21。2.0 把 VectorStore 统一成一个覆盖 20+ 数据库的可移植抽象,具体各实现的配置项以 2.0.1 官方文档为准。
二、VectorStore 可移植抽象
第 9 篇的 ETL 里,业务代码只和 VectorStore 接口打交道:
java
public interface VectorStore {
void add(List<Document> documents); // 入库(内部会向量化)
List<Document> similaritySearch(SearchRequest request); // 相似度检索
}
换库 = 换 VectorStore 的实现 Bean,业务代码零改动:
业务代码
↓ 只依赖接口
VectorStore 接口
├── SimpleVectorStore(内存,测试用)
├── PgVectorStore(PostgreSQL)
├── RedisVectorStore
├── MilvusVectorStore
├── ElasticsearchVectorStore
└── ...(20+ 实现)
三、SQL-like 元数据过滤
向量检索不能只按相似度,还要能按业务字段过滤 (如"只看工程部文档")。Spring AI 用 FilterExpression 提供类似 SQL 的过滤:
java
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
FilterExpression filter = new FilterExpressionBuilder()
.eq("department", "engineering") // 等于
.and()
.gte("year", 2024) // 大于等于
.build();
FilterExpression 会翻译成各向量库自己的过滤语法,配合检索请求使用(第 11 篇展开)。
四、各向量库选型对照表
| 库 | 形态 | 规模 | 过滤能力 | 部署成本 | 适用 |
|---|---|---|---|---|---|
SimpleVectorStore |
内存 | 极小 | 基础 | 零 | 本地开发/单测 |
| PGVector | PostgreSQL 扩展 | 中 | SQL 强 | 低(已有 PG 则免费) | 中小规模、已有 PG |
| Redis | 内存+持久化 | 中 | 中等 | 低 | 低延迟、已有 Redis |
| Milvus | 专用向量库 | 大 | 强 | 高(独立集群) | 亿级向量、专业检索 |
| Elasticsearch | 搜索引擎 | 中 | 强(全文+向量混合) | 中 | 需要混合检索/全文 |
决策口诀:
- 还没定、想快速验证 →
SimpleVectorStore(内存) - 团队已有 PostgreSQL → PGVector(几乎零新增成本,最常用)
- 团队已有 Redis / 要极致延迟 → Redis
- 向量规模大、要专业检索 → Milvus
- 要全文 + 向量混合检索 → Elasticsearch
五、embedding 模型选择与维度一致性
向量库存的是 embedding 向量,维度由 embedding 模型决定,必须和库的建表维度一致。
选择 embedding 模型看三点:
- 维度:常见 768 / 1024 / 1536 等,选定后库建表要一致
- 中文效果:中文场景选中文友好的模型(如 BGE 系列)
- 本地 vs 云端:本地(如 Ollama 的 embedding 模型)免费但需硬件;云端效果好
经验:先定 embedding 模型,再按它的维度建库;中途换模型=换维度=要重建索引,代价大,所以要早定。
六、PGVector 接入实战
6.1 依赖
xml
<!-- PGVector 向量库 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
<!-- PostgreSQL 驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
6.2 建库(先开扩展 + 建表)
sql
-- 启用 pgvector 扩展
CREATE EXTENSION IF NOT EXISTS vector;
建表语句(含向量列,维度与 embedding 模型一致,如 1024):
sql
CREATE TABLE IF NOT EXISTS vector_store (
id uuid PRIMARY KEY,
content text,
metadata json,
embedding vector(1024)
);
6.3 配置与使用
yaml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/ai
username: postgres
password: postgres
业务代码和第 9 篇完全一样,只是 VectorStore 的实现换成了 PgVectorStore(由 starter 自动装配,具体 Bean 配置以 2.0.1 文档为准)。
七、原理图解:VectorStore 抽象层
#mermaid-svg-J9g9f3nxDIMYf9XQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-J9g9f3nxDIMYf9XQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-J9g9f3nxDIMYf9XQ .error-icon{fill:#552222;}#mermaid-svg-J9g9f3nxDIMYf9XQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-J9g9f3nxDIMYf9XQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-J9g9f3nxDIMYf9XQ .marker.cross{stroke:#333333;}#mermaid-svg-J9g9f3nxDIMYf9XQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-J9g9f3nxDIMYf9XQ p{margin:0;}#mermaid-svg-J9g9f3nxDIMYf9XQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-J9g9f3nxDIMYf9XQ .cluster-label text{fill:#333;}#mermaid-svg-J9g9f3nxDIMYf9XQ .cluster-label span{color:#333;}#mermaid-svg-J9g9f3nxDIMYf9XQ .cluster-label span p{background-color:transparent;}#mermaid-svg-J9g9f3nxDIMYf9XQ .label text,#mermaid-svg-J9g9f3nxDIMYf9XQ span{fill:#333;color:#333;}#mermaid-svg-J9g9f3nxDIMYf9XQ .node rect,#mermaid-svg-J9g9f3nxDIMYf9XQ .node circle,#mermaid-svg-J9g9f3nxDIMYf9XQ .node ellipse,#mermaid-svg-J9g9f3nxDIMYf9XQ .node polygon,#mermaid-svg-J9g9f3nxDIMYf9XQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-J9g9f3nxDIMYf9XQ .rough-node .label text,#mermaid-svg-J9g9f3nxDIMYf9XQ .node .label text,#mermaid-svg-J9g9f3nxDIMYf9XQ .image-shape .label,#mermaid-svg-J9g9f3nxDIMYf9XQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-J9g9f3nxDIMYf9XQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-J9g9f3nxDIMYf9XQ .rough-node .label,#mermaid-svg-J9g9f3nxDIMYf9XQ .node .label,#mermaid-svg-J9g9f3nxDIMYf9XQ .image-shape .label,#mermaid-svg-J9g9f3nxDIMYf9XQ .icon-shape .label{text-align:center;}#mermaid-svg-J9g9f3nxDIMYf9XQ .node.clickable{cursor:pointer;}#mermaid-svg-J9g9f3nxDIMYf9XQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-J9g9f3nxDIMYf9XQ .arrowheadPath{fill:#333333;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-J9g9f3nxDIMYf9XQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J9g9f3nxDIMYf9XQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-J9g9f3nxDIMYf9XQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J9g9f3nxDIMYf9XQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-J9g9f3nxDIMYf9XQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-J9g9f3nxDIMYf9XQ .cluster text{fill:#333;}#mermaid-svg-J9g9f3nxDIMYf9XQ .cluster span{color:#333;}#mermaid-svg-J9g9f3nxDIMYf9XQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-J9g9f3nxDIMYf9XQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-J9g9f3nxDIMYf9XQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-J9g9f3nxDIMYf9XQ .icon-shape,#mermaid-svg-J9g9f3nxDIMYf9XQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J9g9f3nxDIMYf9XQ .icon-shape p,#mermaid-svg-J9g9f3nxDIMYf9XQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-J9g9f3nxDIMYf9XQ .icon-shape rect,#mermaid-svg-J9g9f3nxDIMYf9XQ .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J9g9f3nxDIMYf9XQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-J9g9f3nxDIMYf9XQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-J9g9f3nxDIMYf9XQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务代码
只依赖 VectorStore 接口
VectorStore 抽象
SimpleVectorStore
PgVectorStore
RedisVectorStore
MilvusVectorStore
ElasticsearchVectorStore
换库 = 换 B 下面一个节点,A 之上纹丝不动。
八、踩坑 FAQ
坑 1:PGVector 没开扩展
现象 :接入 PGVector 后报找不到 vector 类型。
原因 :没执行 CREATE EXTENSION vector。
解决:先在目标库执行扩展语句,再建表。
坑 2:维度不一致,写入失败
现象 :入库报向量维度不匹配。
原因 :embedding 模型输出维度 ≠ 建表 vector(N) 的维度。
解决 :核对 embedding 维度,重建表(改 vector(N))。
坑 3:metadata filter 表达式语法错
现象 :过滤查询报错或结果为空。
原因 :FilterExpressionBuilder 的字段名/操作符和元数据对不上。
解决 :确认元数据的 key 名一致,用 .eq()/.and()/.gte() 等组合;核对各库翻译后的语法。
坑 4:用内存库跑测试,数据不持久
现象 :SimpleVectorStore 重启数据就没了。
原因 :内存实现,本来就是测试用。
解决:生产用 PGVector/Milvus 等持久化实现。
坑 5:切换向量库后,已有的向量要重建
现象 :从内存切到 PGVector,发现要重新入库。
原因 :向量库之间不通用,切换=重新跑 ETL。
解决:尽早定库;ETL 写成可重跑的幂等脚本,切库后一键重建。
九、小结与合集导航
本篇核心:面向 VectorStore 接口编程,用一张对照表选对库。
VectorStore可移植抽象(2.0 统一,20+ 实现)FilterExpression做 SQL-like 元数据过滤- 选型口诀 + embedding 维度一致性
- PGVector 完整接入
下一篇:《QuestionAnswerAdvisor 检索优化》------RAG 答非所问/幻觉怎么破,从召回优化讲起。
十、互动钩子
投票:你生产用哪个向量库?
- 还没定/内存
- PGVector
- Redis
- Milvus
- Elasticsearch
评论区聊聊你的向量库选型理由;源码见置顶评论。