AI超级智能体项目_踩坑记录_PGVector维度冲突

踩坑记录:Spring AI + PGVector 维度不一致导致 DataIntegrityViolationException

日期:2026-07-23 作者:puipui 影响范围pui-ai-agent RAG 模块 / PGVector 向量存储


一、问题现象

在本地 / 测试环境启动 PgVectorVectorStoreConfigTest 或主程序时,出现以下异常:

text
复制代码
org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [INSERT INTO public.vector_store ...]; ERROR: expected 1536 dimensions, not 1024

表现特征

  • 有时第一次启动成功,重启后立刻失败
  • 报错维度在 1536 / 1024 / 1532 之间反复变化
  • 日志中出现 Is empty: false,说明表已存在但维度不匹配

二、环境信息

组件版本
Spring Boot3.5.7
Spring AI1.1.2
PGVector (Java)0.1.6
PostgreSQL (阿里云 RDS)15.x
Embedding 模型DashScope(通义千问)
JDK21

三、根因分析

3.1 一句话版

DashScope Embedding 模型未显式指定 → 输出维度不稳定 → PGVector 表维度不可变 → 维度冲突。


3.2 PGVector 的硬性约束

sql
复制代码
embedding vector(N)
  • N 在建表时确定,后续无法 ALTER
  • Spring AI 不会自动迁移或重建已有表
  • 一旦维度对不上,100% 报错

3.3 DashScope Embedding 模型维度不固定

模型输出维度是否稳定
text-embedding-v11536
text-embedding-v21536
text-embedding-v31024 / 1536 / 2048
未显式指定SDK 自动选择❌ 不稳定

Spring AI 未指定模型时:

  • 首次启动可能输出 1024
  • 后续启动可能输出 1536
  • → "刚成功又失败"的元凶

3.4 Spring AI 的建表策略

java
复制代码
if (table exists) { // 直接复用已有表结构,完全无视 dimensions 配置 reuse existing schema; } else { // 只有表不存在时才用 dimensions 建表 create table using dimensions; }

关键结论:只要表存在,application.yml 里的 dimensions: 1024 配置完全失效


3.5 阿里云 RDS 的 schema / search_path 干扰

  • 默认 search_path 可能包含多个 schema
  • Spring AI 实际使用的表,可能与 DMS 中看到的不是同一张
  • 典型表现:"我明明删了表,为什么还在报错"

四、排查过程中的典型误区

误区真实情况
以为是 Mockito / JDK 21 问题完全无关,可忽略
只在 application.yml 配置 dimensionsprofile 环境下被覆盖,未生效
postgres 默认库删表实际连接的是业务库 pui_loveAi_agent
只看表是否存在未检查实际维度是否一致
多次重启不清理缓存IDEA / Spring Test 上下文复用旧配置
删表后仍然报错另一个 schema 里还有同名表

五、最终解决方案(✅ 已验证)

Step 1:显式固定 Embedding 模型(最关键)

yaml
复制代码
spring: ai: dashscope: embedding: options: model: text-embedding-v1 # ✅ 固定 1536 维

Step 2:维度与模型强绑定

yaml
复制代码
spring: ai: vectorstore: pgvector: dimensions: 1536 schema: public initialize-schema: true index-type: HNSW distance-type: COSINE_DISTANCE max-document-batch-size: 10000

Step 3:彻底清理旧表

在阿里云 DMS / Navicat 中执行:

sql
复制代码
DROP TABLE IF EXISTS public.vector_store CASCADE;

确认删除成功(应报错):

sql
复制代码
SELECT a.atttypmod - 4 AS dims FROM pg_attribute a JOIN pg_class c ON a.attrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = 'public' AND c.relname = 'vector_store' AND a.attname = 'embedding'; -- 期望结果:ERROR: relation "vector_store" does not exist

Step 4:防止 SQL 初始化脚本干扰

yaml
复制代码
spring: sql: init: mode: never

防止 schema.sql / schema-postgresql.sql 在项目启动前抢先建表。


Step 5:JDBC URL 锁定 schema

yaml
复制代码
spring: datasource: url: jdbc:postgresql://xxx.aliyuncs.com:5432/pui_loveAi_agent?currentSchema=public

Step 6:IDE / 构建环境清理

text
复制代码
1. 停止所有 Run / Debug 进程 2. IDEA → File → Invalidate Caches / Restart 3. mvn clean 4. 重新运行测试

六、成功标志(必须满足)

启动日志中必须看到:

text
复制代码
Using the vector table name: vector_store. Is empty: true Creating table: vector_store embedding VECTOR(1536)

只要看到 VECTOR(1536),问题永久解决。


七、经验总结

7.1 最佳实践清单

  • Embedding 模型必须显式指定(禁止依赖默认值)
  • PGVector 维度一旦确定,不可更改
  • 测试环境每次重建表,避免历史污染
  • 所有向量相关配置集中在 application-local.yml
  • 启动后第一时间检查向量维度
  • 使用「启动自检 Bean」自动校验(见附件)

7.2 推荐稳定组合

text
复制代码
Embedding 模型:text-embedding-v1 向量维度: 1536 索引类型: HNSW 距离算法: Cosine

八、常见 Q&A

Q:我可以用 1024 维吗?

✅ 可以,但必须:

  • Embedding 模型稳定输出 1024
  • 表维度也是 1024
  • 否则必须删表重建

Q:为什么我删了表还是报错?

99% 是以下原因之一:

  • 删错了库 / schema
  • schema.sql 脚本重建了旧表
  • IDEA 缓存未清理

Q:这个坑以后还会遇到吗?

按本文方案配置 + 启动自检 Bean,不会再出现


九、参考报错日志

text
复制代码
Caused by: org.postgresql.util.PSQLException: ERROR: expected 1536 dimensions, not 1024

附件:启动自检 Bean

详见项目源码:

com.puicode.puiaiagent.config.VectorStoreHealthCheck

java
复制代码
package com.puicode.puiaiagent.config; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.vectorstore.pgvector.PgVectorStore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; /** * 向量存储启动自检器 * * <p>功能:在应用启动时自动校验以下三项一致性,防止维度不匹配问题:</p> * <ol> * <li>Embedding 模型实际输出维度</li> * <li>PGVector 表的实际维度</li> * <li>配置中声明的维度</li> * </ol> * * <p>任意一项不匹配,立即 Fail Fast,抛出明确异常,阻止应用继续启动。</p> * * @author Shihui * @date 2026-07-23 */ @Slf4j @Configuration @ConditionalOnBean({EmbeddingModel.class, PgVectorStore.class}) @ConditionalOnProperty( name = "spring.ai.vectorstore.pgvector.initialize-schema", havingValue = "true", matchIfMissing = false ) public class VectorStoreHealthCheck { private final EmbeddingModel embeddingModel; private final JdbcTemplate jdbcTemplate; private final org.springframework.core.env.Environment environment; public VectorStoreHealthCheck( EmbeddingModel embeddingModel, JdbcTemplate jdbcTemplate, org.springframework.core.env.Environment environment) { this.embeddingModel = embeddingModel; this.jdbcTemplate = jdbcTemplate; this.environment = environment; } @PostConstruct public void validateVectorStoreConsistency() { log.info("========================================"); log.info("🔍 [向量存储自检] 开始启动校验..."); log.info("========================================"); // 1️⃣ 检查 Embedding 模型输出维度 int modelDimension = checkEmbeddingModelDimension(); // 2️⃣ 检查 PGVector 表维度 int tableDimension = checkTableDimension(); // 3️⃣ 检查配置声明的维度 Integer configDimension = checkConfigDimension(); // 4️⃣ 一致性校验 validateConsistency(modelDimension, tableDimension, configDimension); log.info("========================================"); log.info("✅ [向量存储自检] 全部通过,维度一致 = {}D", modelDimension); log.info("========================================"); } /** * 检查 Embedding 模型实际输出维度 */ private int checkEmbeddingModelDimension() { try { var response = embeddingModel.call( new EmbeddingRequest(List.of("test")) ); int dim = response.getResult().getOutput().length; log.info("📐 Embedding 模型输出维度: {}D", dim); return dim; } catch (Exception e) { throw new IllegalStateException( "❌ [向量存储自检] Embedding 模型调用失败,请检查 DashScope API Key 和网络连接", e); } } /** * 检查 PGVector 表的实际维度 */ private int checkTableDimension() { String sql = """ SELECT a.atttypmod - 4 AS dims FROM pg_attribute a JOIN pg_class c ON a.attrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = 'public' AND c.relname = 'vector_store' AND a.attname = 'embedding' """; try { Integer dim = jdbcTemplate.queryForObject(sql, Integer.class); if (dim == null) { throw new IllegalStateException( "❌ [向量存储自检] vector_store 表不存在或 embedding 列不存在," + "请确认 initialize-schema: true 且表已正确创建" ); } log.info("📐 PGVector 表实际维度: {}D", dim); return dim; } catch (Exception e) { if (e instanceof IllegalStateException) { throw e; } throw new IllegalStateException( "❌ [向量存储自检] 查询 vector_store 维度失败: " + e.getMessage(), e); } } /** * 检查配置中声明的维度 */ private Integer checkConfigDimension() { String dimStr = environment.getProperty( "spring.ai.vectorstore.pgvector.dimensions"); if (dimStr == null || dimStr.isBlank()) { log.warn("⚠️ [向量存储自检] 配置中未声明 spring.ai.vectorstore.pgvector.dimensions," + "强烈建议显式指定以避免维度漂移"); return null; } int dim = Integer.parseInt(dimStr); log.info("📐 配置声明维度: {}D", dim); return dim; } /** * 校验三者一致性 */ private void validateConsistency(int modelDim, int tableDim, Integer configDim) { // 模型维度 vs 表维度(必须一致) if (modelDim != tableDim) { throw new IllegalStateException(String.format( "❌ [向量存储自检] 维度不匹配!\n" + " Embedding 模型输出: %dD\n" + " PGVector 表维度: %dD\n" + " 解决方案:\n" + " 1. 确认 spring.ai.dashscope.embedding.options.model 已固定\n" + " 2. DROP TABLE public.vector_store CASCADE;\n" + " 3. 重启应用让 Spring AI 用正确维度重建表\n" + " 详细排查步骤见 docs/踩坑记录-PGVector维度冲突.md", modelDim, tableDim )); } // 配置维度 vs 实际维度(如果配置了的话) if (configDim != null && !configDim.equals(tableDim)) { log.warn("⚠️ [向量存储自检] 配置维度({}D) 与实际表维度({}D) 不一致," + "但模型输出与表维度匹配,应用可正常运行。" + "建议将配置改为 {}D 以保持一致。", configDim, tableDim, tableDim); } } }

功能

  • 启动时自动校验 Embedding 模型输出维度
  • 自动校验 PGVector 表维度
  • 不一致时 Fail Fast,直接抛出明确异常
  • 防止后人再次踩坑
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
puipui
下载 APP