向量数据库太贵?pgvector 让你省下一台服务器

事情是这样的。

半年前我搞了个内部知识库问答系统,文档量大概五万篇。刚开始我兴冲冲地搭了 Milvus,搞了一天部署,又花了一天写接入代码。跑是跑起来了,但每个月服务器成本多了小一千块,运维还时不时来问“这个 etcd 报警是啥意思”。

我一个写 Java 的,真不想当向量库的保姆。

后来我一拍脑袋:我们项目本来就用的 PostgreSQL,它不是有个 pgvector 插件吗?干脆全塞进 PG 里,少一个依赖,少一份维护。

半年下来,系统跑得稳稳当当,省了钱,也省了命。今天就把 pgvector 从安装到生产实战的完整经验分享出来,给想用 PG 搞定向量存储的兄弟们一个省流版。

一、pgvector 是个啥?一句话:让 PostgreSQL 能存向量、查向量

pgvector 是 PostgreSQL 的一个扩展插件。装完之后,你的 PG 就多了一种叫 vector 的数据类型,以及一套专门查向量相似度的运算符。

它的优点就四个字:就地取材。你项目里已经在用 PG 了,加个插件就能用向量,不用再部署一套新数据库,不用学新查询语言,SQL 一把梭。业务数据和向量数据还能放一个库里,做 JOIN 查询爽得飞起。

当然它也不是万能的。单表几百万向量以内、QPS 几百以内,pgvector 基本能扛。超过这个量级,你可能还是得考虑 Milvus 这类专用库。但说实话,绝大多数项目根本到不了那个量级。

二、安装:比你想象中简单一万倍

你的 PG 版本只要 12 以上,就能装。我用的是 PG 15。

如果你用云数据库(比如阿里云 RDS、AWS RDS),控制台里直接勾选 pgvector 扩展就行,什么都不用装。

如果你是自建 PG,一行命令:

bash # 先装编译工具和 PG 开发头文件 apt install postgresql-server-dev-15 git make gcc # 克隆 pgvector 源码并编译安装 git clone https://github.com/pgvector/pgvector.git cd pgvector make make install

然后进你的目标数据库,敲一句:

sql CREATE EXTENSION vector;

搞定。你现在已经拥有向量存储能力了。不用配置文件,不用起新进程,不用学新端口。

三、建表:把向量当一种普通字段来用

pgvector 用起来的感觉,就是给表加了一个 vector 类型的列。跟你加一个 varchar 列一样自然。

sql CREATE TABLE documents ( id BIGSERIAL PRIMARY KEY, title VARCHAR(500), content TEXT, embedding VECTOR(768) – 768 是向量维度,跟你用的 Embedding 模型对齐 );

这里 768 是固定的,取决于你用的 Embedding 模型输出。我用的是 BGE-M3,输出 1024 维,这里就写 VECTOR(1024)。用 OpenAI 的 text-embedding-3-small 就写 VECTOR(1536)。

存数据也简单:

sql INSERT INTO documents (title, content, embedding) VALUES ( ‘如何配置Redis缓存’, ‘Redis缓存的配置步骤如下…’, ‘[0.023, -0.145, 0.678, …]’::vector – 你的向量数组 );

跟普通 INSERT 没有本质区别,就是字段值是一个浮点数数组,用 ::vector 转换一下。

四、查询:三种相似度,挑适合你的用

pgvector 提供了三种向量距离运算符:

运算符 含义 适用场景
<=> L2 距离(欧氏距离) 值越小越相似,适合大多数场景
<#> 内积(负内积) 值越大越相似,适合需要计算方向强度的场景
<=> 余弦距离 值越小越相似,适合只关心方向不关心长度的场景

最常用的是余弦距离,因为 Embedding 模型大多用余弦相似度训练,你查的时候也用余弦距离,匹配度最高。

查一个问题的相似文档:

sql SELECT title, content, 1 - (embedding <=> ‘[0.023, -0.145, …]’::vector) AS similarity FROM documents ORDER BY embedding <=> ‘[0.023, -0.145, …]’::vector LIMIT 5;

这里 embedding <=> query_vector 返回的是余弦距离(0 表示完全相同,2 表示完全相反),所以我用 1 - 余弦距离 转成相似度,方便看。

如果你用的是 L2 距离,直接 ORDER BY embedding <-> query_vector,值越小的排越前。

五、索引:不加索引,百万数据查一次要 5 秒

pgvector 支持两种向量索引:IVFFlatHNSW

IVFFlat:先对向量做 K-means 聚类,查询时只搜最近的几个聚类中心,牺牲一点精度换速度。适合数据量大、对精度要求不极致的场景。

HNSW:基于图的近似搜索,查询速度快,精度高,但建索引慢、占内存多。适合对查询延迟要求高的场景。

我怎么选的:五万篇文档,用 HNSW 太奢侈,IVFFlat 足够。建索引之前,查一次要 3-5 秒。建完索引,降到 50 毫秒。

建索引语法:

sql – 先往表里插够数据再建索引,IVFFlat 需要先了解数据分布 CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

这里的 lists 是聚类中心数。经验值:数据量在 10 万以内,lists 设为数据量的平方根左右,比如 5 万条数据,lists = 224。我 5 万条文档,设了 200,效果不错。

如果用 HNSW:

sql CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);

踩坑提醒:IVFFlat 索引需要表里先有一定量的数据(至少几千条)再建,否则聚类不准,查询效果差。而且每次大量增删数据后,最好重建索引,因为数据分布变了。

六、Java 代码实战:Spring Boot + pgvector 的正确打开方式

重头戏来了。Java 里怎么操作 pgvector?用 MyBatis-Plus 或者 JdbcTemplate 都行,关键是处理 vector 类型的读写。

方案一:原生 JDBC + pgvector 驱动(推荐)

pgvector 官方提供了 JDBC 支持,加依赖:

xml com.pgvector pgvector 0.1.6

然后配置数据源的时候,用 PGvector 提供的扩展:

java // 注册 vector 类型的转换 PGvectorDataSource dataSource = new PGvectorDataSource(); dataSource.setUrl(“jdbc:postgresql://localhost:5432/yourdb”); dataSource.setUser(“user”); dataSource.setPassword(“password”);

Spring Boot 项目里,你可以在配置类里手动包装一下 DataSource,或者直接用 JdbcTemplate 配合 PGvector 的方法。

插入向量

java String sql = “INSERT INTO documents (title, content, embedding) VALUES (?, ?, ?)”; // embedding 是一个 float[] 数组 PGvector vector = new PGvector(embedding); jdbcTemplate.update(sql, title, content, vector);

查询向量

java String sql = "SELECT title, content, 1 - (embedding <=> ?) AS similarity " + “FROM documents ORDER BY embedding <=> ? LIMIT 5”; PGvector queryVec = new PGvector(queryEmbedding); List<Map<String, Object>> results = jdbcTemplate.queryForList(sql, queryVec, queryVec);

方案二:MyBatis-Plus + 自定义 TypeHandler(如果你非要用 MP)

MP 默认不认识 vector 类型,你得自己写一个 TypeHandler:

java @MappedTypes(PGvector.class) public class PGvectorTypeHandler extends BaseTypeHandler { @Override public void setNonNullParameter(PreparedStatement ps, int i, PGvector parameter, JdbcType jdbcType) throws SQLException { ps.setObject(i, parameter); } // 其他方法略,照着官方文档写 }

然后在 Entity 的字段上标注:

java @TableField(typeHandler = PGvectorTypeHandler.class) private PGvector embedding;

我个人更推荐用原生 JDBC 或者 JdbcTemplate。逻辑清晰,不会因为 ORM 层出幺蛾子。向量操作本来就不复杂,不需要 MP 的那套自动映射。

七、生产环境避坑三件套

坑 1:向量维度不一致,插入直接报错

你在建表时指定了 VECTOR(768),如果传进来一个 1024 维的向量,PG 直接报错。所以一定要在建表前确认 Embedding 模型的输出维度,代码里加个校验,防止模型切换后翻车。

坑 2:索引不维护,越查越慢

IVFFlat 索引依赖数据分布。你的文档每天都在增删改,数据分布变了,索引却还是旧的。我的做法是每周自动执行一次 REINDEX INDEX CONCURRENTLY idx_documents_embedding,不锁表,慢慢重建。

坑 3:连接池没配好,向量查询占满连接

向量查询比普通 SQL 慢,尤其是建索引之前或者查询量大时。如果你的数据库连接池太小,几个慢查询就会占满所有连接,导致整个系统不可用。我给向量查询单独配了一个稍大的连接池,跟业务查询隔离开。

八、性能实测:什么量级该考虑转场

我给兄弟们一个实测参考(PG 15,8C16G,SSD):

  • 10 万条向量,无索引:单次查询 2-3 秒
  • 10 万条向量,IVFFlat 索引:单次查询 30-80 毫秒
  • 50 万条向量,IVFFlat 索引:单次查询 100-200 毫秒
  • 100 万条向量,HNSW 索引:单次查询 50-150 毫秒
  • 500 万条向量,HNSW 索引:单次查询 500 毫秒以上,内存占用飙升

结论:百万级以内,pgvector 基本够用。超过百万级,且对查询延迟要求高(<100ms),建议上 Milvus 或 Qdrant。我的项目才五万篇文档,离这个瓶颈远得很。

九、总结

pgvector 不是一个“凑合”的方案,它是一个非常务实的选择。对大多数中小型项目来说,它性能完全够用,运维成本几乎为零,还能跟业务数据无缝集成。

如果你现在用的就是 PostgreSQL,先试试 pgvector。别一上来就引入新数据库,给自己加复杂度。等你真的碰到它的性能天花板时,那时候再迁移也不迟——而且那时候你更清楚自己要什么了。

毕竟,我们写后端的第一信条:能用已有的组件解决的问题,绝不引入新组件。

项目

项目适用人群:做课设、毕设的小伙伴、只学习了后端(或者前端),但想要自己做项目写在简历上,这三个项目可以作为拓展点,RAG+MCP+Skill 智能商城购物平台制作中~

前后端项目 Gitee & Github 累计 3000+ Star,10W+浏览量!⭐点赞⭐收藏⭐不迷路!⭐

智能 AI 旅游推荐平台:

https://github.com/luoye6/vue3_tourism_frontend

https://gitee.com/xiaobaitiao_z/vue3_tourism_frontend

智能 AI 校园二手交易平台:

https://github.com/luoye6/vue3_trade_frontend

https://gitee.com/xiaobaitiao_z/vue3_trade_frontend

GPT 智能图书馆:

https://github.com/luoye6/Vue_BookManageSystem

https://gitee.com/xiaobaitiao_z/Vue_BookManageSystem

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐