内存/Redis/PGVector/Milvus/ES 怎么选?Spring AI VectorStore 抽象与向量库选型对照表

内存/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 模型看三点:

  1. 维度:常见 768 / 1024 / 1536 等,选定后库建表要一致
  2. 中文效果:中文场景选中文友好的模型(如 BGE 系列)
  3. 本地 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

评论区聊聊你的向量库选型理由;源码见置顶评论。

相关推荐
hweiyu002 小时前
Redis命令:SUNIONSTORE
redis·缓存
麻瓜code3 小时前
秒杀优化三连:Lua + 阻塞队列 + Redis Stream,响应从 3.4 秒到毫秒级
redis·junit·lua
ynchyong4 小时前
OpenAI 技术全景解析
openai·rag·function·guardrails
没有气的汽水6 小时前
一次生产 Redis 内存打满的排查、清理和复盘
redis
hey you~7 小时前
云呼叫中心坐席自动分配实战:ACD 策略、Redis+Lua 实现与排查清单
数据库·redis·lua·云呼叫中心·acd·坐席分配·坐席状态机
我不会起名字3229 小时前
Redis pipeline 用了反而更慢:3 类误用与正确的批量姿势
redis·pipeline·lua·rtt·批量
Tsuki_tl9 小时前
【总结】Redis五大数据类型应用场景及基本命令
数据库·redis·后端·缓存·缓存中间件·redis命令·redis 五大数据类型
Elastic 中国社区官方博客9 小时前
基于 Jina OCR v1 与 Elasticsearch 的 PDF 智能混合检索与 RAG 系统实战指南
大数据·数据库·elasticsearch·搜索引擎·ocr·jina
Eloudy21 小时前
Qwen2.5-14B-Instruct-AWQ 模型文件结构分析
大数据·elasticsearch·搜索引擎