向量数据库太贵?pgvector 让你省下一台服务器
pgvector 不是一个“凑合”的方案,它是一个非常务实的选择。对大多数中小型项目来说,它性能完全够用,运维成本几乎为零,还能跟业务数据无缝集成。如果你现在用的就是 PostgreSQL,先试试 pgvector。别一上来就引入新数据库,给自己加复杂度。等你真的碰到它的性能天花板时,那时候再迁移也不迟——而且那时候你更清楚自己要什么了。能用已有的组件解决的问题,绝不引入新组件。
向量数据库太贵?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 支持两种向量索引:IVFFlat 和 HNSW。
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
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)