AI超级智能体项目_踩坑记录_PGVector维度冲突
踩坑记录:Spring AI + PGVector 维度不一致导致 DataIntegrityViolationException
日期:2026-07-23 作者:puipui 影响范围:
pui-ai-agentRAG 模块 / 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 Boot | 3.5.7 |
| Spring AI | 1.1.2 |
| PGVector (Java) | 0.1.6 |
| PostgreSQL (阿里云 RDS) | 15.x |
| Embedding 模型 | DashScope(通义千问) |
| JDK | 21 |
三、根因分析
3.1 一句话版
DashScope Embedding 模型未显式指定 → 输出维度不稳定 → PGVector 表维度不可变 → 维度冲突。
3.2 PGVector 的硬性约束
▼sql复制代码embedding vector(N)
N在建表时确定,后续无法 ALTER- Spring AI 不会自动迁移或重建已有表
- 一旦维度对不上,100% 报错
3.3 DashScope Embedding 模型维度不固定
| 模型 | 输出维度 | 是否稳定 |
|---|---|---|
text-embedding-v1 | 1536 | ✅ |
text-embedding-v2 | 1536 | ✅ |
text-embedding-v3 | 1024 / 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 配置 dimensions | profile 环境下被覆盖,未生效 |
在 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个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
内容推荐
AI 超级智能体 - 历史消息存储Advisor
4
AI 超级智能体 - 基于mysql的消息存储
4
SpringAl+RAG+MCP全栈|AI超级智能体企业级实战项目中:stdio连接方式调用MCP服务问题运行测试时解决:1. 先查看MCP server那的yml文件,文件名有没有写错2. 修改server端的"application-stdio.yml",把每一个报错发给deepseek再验证之后,得出结论:Server 的 stdout 完全被污染了:Spring Boot Banner 完
3
SpringAI + RAG + MCP 全栈 | AI 超级智能体企业级实战(26年必学) - 第三期:基础内容 笔记
3
Code Log-AI超级智能体项目,开发日志。SpringAI + RAG + MCP 全栈 | AI 超级智能体企业级实战(26年必学)
3
作者分享
大家好,我想知道现在市场上初级程序员的需求还多吗,因为已经毕业一年了,但是还没有做过开发岗,这一年里做过两份工作,一个是产品的实习,一个算是巡店系统的维护吧,说是维护其实只是一些简单的功能测试(没有参与开发)。打算8月份9月份开始投初级开发(java),简历必须要有大模型相关的项目吗?简历准备写一个伙伴匹配系统(额外拓展一些功能)和一个oj判题项目,这两个够嘛?找小公司的话还需要花时间准备算法吗?双非,目前在上海,小公司就行,薪资有个6或7k左右就满意了
🥺🥺🥺求助
3
day5
端午节偷懒了,最近还在处理离职的事情也影响了一些进度。
处理的差不多了,继续续上
今天完成了主页性能优化的分布式锁和开启了组队功能的系统设计部分,明天继续学习组队功能的代码💪
1
伙伴匹配系统遇到的坑(已解决)
3
day4
完成了用户搜索页的前端和后端部分
学习了knife4j文档的使用
因为伙伴匹配系统是基于用户中心系统的,在通勤间隙把用户中心系统也补看一下
3
day3
今天完成了伙伴匹配项目-标签搜索功能的前端页面开发。前端的代码看着好懵,只能先跟着鱼皮敲一遍,搭配AI一点点理解个大概,先跟完项目后面有时间再来补吧😫
---补了一个java8知识点stream流
2
