
自从大语言模型火起来之后怎么把私有数据“喂”给模型成了一个绕不开的话题。市面上冒出来一堆向量数据库但如果你本身就在用 PostgreSQL其实不用急着引入新组件——扩展 pgvector 就能让老牌关系型数据库原生支持向量存储和相似度检索直接在这一套体系里把语义搜索和 RAG 做起来。这篇东西我会从为什么选 pgvector、环境怎么搭、核心 SQL 怎么写、到完整跑通一个 RAG 流程全部过一遍顺便把我在实操中踩过的坑一并交代清楚。整篇文章尽量做到“拿来即用”适合那些想把 PostgreSQL 变成语义搜索后端的开发者也包括正在折腾本地知识库、还没想好底层存什么的朋友。1. 为什么用 pgvector 而不是专用向量数据库1.1 语义搜索和 RAG 到底需要什么先说清楚一个底层逻辑。语义搜索的本质是把文本转换成一组高维向量然后通过向量之间的“距离”来衡量语义上的相似程度。“苹果好吃”和“苹果公司发布了新手机”这两句话如果用关键词匹配第一句和第二句几乎不相干但映射到向量空间之后那句关于苹果公司的话会离科技新闻更近而不是离水果更近。这个过程靠的是嵌入模型Embedding Model它把自然语言压缩成几百上千维的数字数组。RAGRetrieval-Augmented Generation检索增强生成是建立在语义搜索之上的一个工程框架。它的核心思路很简单大模型不擅长记住你私有数据的细节尤其是那些频繁变动的业务资料所以 RAG 的做法是——先把文档切成小块、嵌入成向量、存进数据库等到用户提问题时再从库里把最相关的若干块文本检索出来拼进 Prompt 里交给大模型让模型“开卷考试”。所以不管语义搜索还是 RAG底层都逃不开三件事向量化、向量存储、向量检索。pgvector 解决的就是后面两个而且是在你熟悉的 SQL 语境里解决。1.2 三个方案放在一起对比差异在工程成本很多项目最初犯的毛病是“为了用向量数据库而用向量数据库”。我见过一个团队业务库里就几千条数据非要引入一套 Milvus 集群前后端对接、运维部署、数据同步搞了一个多月。其实这笔账是可以提前算清楚的。方案存储层学习成本运维成本适合场景pgvector复用现有 PostgreSQL 存储、备份、权限体系只需学几个新 SQL 函数极低一个扩展搞定中小规模百万级以内已有 PostgreSQL 体系专用向量数据库如 Milvus、Qdrant、Weaviate独立组件需要单独维护需要掌握各自 SDK 和部署方式较高涉及集群、监控、备份独立处理大规模向量检索、过滤条件复杂、对性能要求极高的场景关系表手写余弦相似度PostgreSQL 原生浮点数组没有额外依赖低但性能极差仅限演示或数据量很小、不在乎性能pgvector 最大的优势在于“事务一致性”和“SQL 生态”。你的业务数据、用户信息、文档内容都存在 PostgreSQL如果向量也要入库用 pgvector 就能在同一个事务里完成“更新文档 更新向量”不需要分布式事务也不用维护两套存储。1.3 pgvector 的工作原理与核心概念简单拆一下 pgvector 的内部机制。它本质上是一种“自定义类型 索引方法 运算符”的组合扩展。它给 PostgreSQL 增加了一种叫vector的数据类型可以存放固定维度的浮点数组同时提供三种距离运算符-表示欧几里得距离L2、表示余弦距离、表示内积点积的反向。查询的时候PostgreSQL 可以通过这些运算符做排序配合专门的向量索引加速检索。关于向量的维度一个容易混淆的点是Embedding 模型输出的维度是固定的比如 OpenAI 的 text-embedding-3-small 是 1536 维开源的中文模型如 text2vec-large-chinese 是 1024 维。所以建表的时候那一列的类型可以写成vector(1536)、vector(1024)这个括号里的数字必须和实际向量维度一致否则写入数据时会报错。索引方面pgvector 早期主要靠 IVFFlat倒排文件 扁平量化后来支持了 HNSW分层可导航小世界图。两者都是近似最近邻ANN算法都不保证绝对召回全部最相似的结果但换来的是检索速度的提升。对绝大多数 RAG 场景来说“极快”比“绝对精确”重要得多因为相似度排序前几名往往就是用户要的答案。2. 环境准备与安装Docker 和本地部署两条路线2.1 快速体验用 Docker三十秒起飞如果只是先跑通验证我强烈建议直接用 Docker 镜像省去编译安装的折腾。pgvector 官方在 Docker Hub 上提供了带扩展的镜像比如pgvector/pgvector:pg16意思是 PostgreSQL 16 版预装了 pgvector。# 拉取并启动一个带 pgvector 的 PostgreSQL 16 实例 docker run --name pgvector-test \ -e POSTGRES_PASSWORDyourpass \ -p 5432:5432 \ -d pgvector/pgvector:pg16启动之后普通 PostgreSQL 客户端连接上去直接执行CREATE EXTENSION IF NOT EXISTS vector;就能用。如果是在公司内部不能访问 Docker Hub 的环境也可以用postgres:16基础镜像进入容器后通过 apt 安装 postgresql-16-pgvector 再 enable但那样多几步操作不如直接用官方镜像省事。需要注意版本选择。网上搜“postgresql 下载哪个版本”能搜出一堆结果但如果你用 pgvector建议至少 PostgreSQL 14 以上。pgvector 的 HNSW 索引需要 PostgreSQL 12 以上才能支持而 16 是当前功能最完整、社区活跃度最高的版本适配国产 Linux 发行版也很稳。2.2 生产环境用裸机安装以 Ubuntu 为例生产环境不想用 Docker或者需要直接跑在宿主机上编译安装 pvvvector 其实也不复杂核心三步装 PostgreSQL、装 pgvector、建扩展。# 第一步安装 PostgreSQL 16这里用官方 apt 源 sudo apt install -y postgresql-common sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y sudo apt install -y postgresql-16 postgresql-client-16 # 第二步安装 pgvector sudo apt install -y postgresql-16-pgvector # 第三步进入数据库创建扩展 sudo -u postgres psql CREATE EXTENSION vector;内置源码编译也是一样的逻辑只不过多一步make make install。这里有个小坑值得提前说明如果 PostgreSQL 是源码编译安装的那么 pgvector 也要编译安装且pg_config路径必须指向同一个 PostgreSQL 版本否则安装完会出现 “extension control file not found” 之类的报错。装完以后随便连个库跑一下SELECT vector [1,2,3];能正常返回就说明环境通了。2.3 安装完成后必须做的两个检查第一个检查是确认扩展可见。PostgreSQL 的扩展默认安装在pg_catalog之外所以每个用到的业务库都要单独执行CREATE EXTENSION vector;只在默认库postgres中创建是远远不够的。第二个检查是确认shared_preload_libraries配置。虽然 pgvector 本身不需要预加载但如果后面想用 HNSW 索引并且要开启并行构建建议在postgresql.conf里给shared_preload_libraries加上vectors这个可选模块用于提升整体性能。不过绝大多数中小型项目不配这个也能跑可以先跳过。3. 语义搜索的完整实现从建表到相似度查询3.1 建表一张表同时存文本和向量语义搜索不是只在表里存向量就完事而是要时刻保留“原文”——因为检索出来最终要展示给人看、或者拼进 Prompt 给大模型看。所以设计表结构的时候至少包含三列唯一 ID、原始文本、向量。-- 创建一个简单的文档表 CREATE TABLE documents ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, embedding VECTOR(1024) -- 这里维度要和嵌入模型输出对齐 ); -- 创建一个带业务标签的示例方便过滤查询 CREATE TABLE articles ( id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL, category TEXT, content TEXT NOT NULL, embedding VECTOR(1024) );我见过很多初次上手的人把VECTOR的维度写成 0 或者不写那样也能建表但插入数据时会得到 “expected N dimensions, not M” 的报错。所以建表之前先确认嵌入模型的输出维度写错维度在数据量小的时候不容易发现数据一多回填时就一脸懵。3.2 生成向量用 Python LangChain 或其他库填充数据在实际项目里向量不是手写的而是通过嵌入模型生成的。这里我以 OpenAI 的接口为例但要注意公司内部或本地私有化部署时常用的是基于 BGE、M3E、text2vec 等模型自建服务代码逻辑完全一样只是换了 API 地址和模型名。from openai import OpenAI import psycopg2 client OpenAI(api_key你的key, base_url你自己的地址) texts [PostgreSQL 是最先进的开源关系型数据库, 向量数据库擅长模糊匹配和语义检索, RAG 让大模型学会使用企业私有知识] vectors [] for t in texts: resp client.embeddings.create( modeltext-embedding-3-small, inputt ) vectors.append(resp.data[0].embedding) # 写入 PostgreSQL conn psycopg2.connect(dbnametest userpostgres passwordyourpass) cur conn.cursor() for text, vec in zip(texts, vectors): cur.execute( INSERT INTO documents (content, embedding) VALUES (%s, %s::vector), (text, vec) ) conn.commit()这段代码是个最小闭环核心就是“文本 → 向量 → 入库”。值得留意的细节是%s::vector这个写法如果直接传 Python 的 list 进去PostgreSQL 会因为类型不明而报错显式加上::vector才能让 PG 识别目标类型。有一个很多新手容易犯的错整篇文章直接生成一个向量。实操下来效果并不好因为一篇长文档的语义会“被平均”检索时很难精确定位到段落级别的内容。所以建知识库的时候前提工作是把长文本切片比如按 500 字一块、或者按 Markdown 标题分段然后再逐块向量化。这也是 RAG 项目里常说“文本切分决定 RAG 上限”的原因。3.3 创建索引IVFFlat 和 HNSW 的选型数据量超过几千条之后全表扫描算相似度会明显变慢。这时需要建向量索引。pgvector 支持两种索引我分别说下适用场景。IVFFlat把向量空间划分成若干个列表lists检索时只搜最近几个列表。优点是构建快、内存占用低缺点是精度受 lists 参数影响且数据要提前有“足够样本”才能建出好索引。通常在表里已经有大量数据后再建lists 一般取sqrt(总行数)比如 10 万行取 316 左右。HNSW基于图的近似最近邻算法检索精度高速度也快构建时比较吃内存但对参数不敏感上手难度低。官方建议小规模数据直接用它尤其是并发查询不高但要求准确率的场景。-- 用 HNSW 索引向量距离用余弦 CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops); -- 用 IVFFlat 索引lists 根据数据量设置 CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists 100);这里有个概念必须纠正pgvector 的索引是在CREATE INDEX时通过指定opsoperator class来决定距离类型的上面vector_cosine_ops代表余弦距离索引vector_l2_ops代表欧几里得距离索引vector_ip_ops代表内积索引。选错了的话索引可能不会被查询计划器使用。实际项目里我通常默认推荐 HNSW因为省心、精度好尤其在十几万行数据以内构建速度差不太多。IVFFlat 更适合百万级以上但你能忍受一定精度损失、且索引构建窗口有限的超大场景。3.4 执行语义查询三个距离函数与召回逻辑语义搜索的查询语句看起来非常简单但背后有一些细节值得斟酌。-- 按余弦距离升序取最相似的 5 条 SELECT id, content, 1 - (embedding [...]) AS similarity FROM documents ORDER BY embedding [...] LIMIT 5;很多初学者搞不清和相似度的区别。返回的是向量之间的“余弦距离”范围是 0 到 2 之间对归一化向量而言是 0 到 2距离越小表示越相似。如果你想让结果看起来更像“百分制相似度”就用1 - distance换算不过实际效果取决于嵌入模型本身的性质不一定离 1 越近就一定越相关。另一个容易忽略的点是阈值过滤。光靠LIMIT 5会无条件返回 5 条哪怕这 5 条其实全都跟问题不相关。所以生产级语义搜索一定要加一个最低相似度阈值比如只保留1 - (embedding [...]) 0.6的结果这个阈值靠实际数据调试。如果一张表里有分类字段查询时可以先按标签过滤、再算语义相似度比如“只搜科技类文章”。这其实就是把 SQL 关系型过滤和向量检索结合起来的优势专用向量数据库处理这种带复杂条件过滤的场景反而麻烦。4. 从语义搜索到 RAG构建知识库与应用链路4.1 RAG 的整体流程与各环节作用RAG 不只是“查出来拼给大模型”这么简单它的完整链路是文档加载 → 文本切分 → 向量化 → 入库 → 用户提问 → 向量检索 → 构造 Prompt → 大模型回答。上一个章节我们把“入库 检索”做完了这一章重点看如何把它串成一个知识库应用。一句话概括 RAG 的价值它把大模型的“记忆”问题外包给了数据库。模型不再需要把知识全部塞在权重里而是每次回答前先从库里拿参考资料。这样做的好处有两个一是知识更新只需要改数据库不需要重新训练模型二是回答可以追溯到原文减少大模型“一本正经地胡说八道”的概率。4.2 文档切分这一步直接决定回答质量如果只让我提一个 RAG 项目的关键成功因素我会选文本切分。向量化之后的检索单元是“文本块”块太小则上下文不连贯块太大则检索结果里混入太多噪音词。写一下我常用的经验值通用文档按固定长度切分每个块 300~800 字重叠 50~100 字。技术文档 / Markdown优先按标题层级切其次是按段落切。PDF 扫描件先 OCR 再切不然切出来一堆乱码向量化后检索效果极差。代码仓库按函数、类、方法切或者按文件切避免把多个不相关函数揉在一起。用 LangChain 的RecursiveCharacterTextSplitter是很多项目的标准做法因为它会按\n\n、\n、。、空格这样的优先级递归切分能尽量让一个块内语义完整。经验上不要盲目把 chunk_size 调得很大500 到 800 之间最为平衡。from langchain.text_splitter import RecursiveCharacterTextSplitter text 这是一篇很长的文档…… splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , ] ) chunks splitter.split_text(text)说到这里热词里有“rag知识库能存储图片嘛”。我的答案是pgvector 本身存的是向量图片内容可以走多模态模型向量化之后把向量存进 pgvector图片文件本身建议还是放对象存储库里存路径和向量。这样既有语义检索能力又不会把数据库撑爆。4.3 组装 Prompt把检索结果变成答案的素材检索只是手段最终用户看到的是大模型生成的回答。检索到若干条文本块之后要设计一个 Prompt 模板把每个块的引用内容传进去。results 【原文1】PostgreSQL 具备完善的事务和 MVCC 机制... 【原文2】pgvector 扩展使得关系库能够存储向量数据... prompt f 请根据以下参考资料回答用户的问题。 如果资料中没有答案请直接说不知道不要编造。 参考资料 {results} 用户问题PostgreSQL 怎么实现语义搜索 这段代码的思路很直白检索到的 chunk 是“闭卷考试时的翻书材料”问题则来自用户。模板里的“没有答案就承认不知道”非常重要这是降低幻觉的核心手段。很多 RAG 框架默认带了类似的提示词但自己搭的时候很容易漏掉这句导致模型在检索结果不相关时依然强行回答反而污染了输出质量。4.4 一个可用 end-to-end 的 RAG 查询函数把所有环节拼起来实现一个函数输入用户问题输出基于知识的回答。def ask(question: str, k: int 4): # 1. 查询向量化 q_vec client.embeddings.create( modeltext-embedding-3-small, inputquestion ).data[0].embedding # 2. 向量检索 cur.execute( SELECT content, 1 - (embedding %s::vector) AS score FROM documents ORDER BY embedding %s::vector LIMIT %s , (q_vec, q_vec, k)) rows cur.fetchall() # 过滤低分结果 contexts [r[0] for r in rows if r[1] 0.5] if not contexts: return 知识库中没有找到相关内容。 # 3. 组装 Prompt 并调用大模型 prompt f 你是知识库问答助手请严格基于以下资料回答。 资料 {chr(10).join(contexts)} 问题{question} 如果资料不相关请回复抱歉我无法从现有知识库中找到答案。 resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}] ) return resp.choices[0].message.content这个函数展示了完整闭环实际项目里可以再优化两点一是把知识库按用户或部门做隔离在查询 SQL 里加条件过滤二是把检索到的原文和模型回答一起返回方便前端展示“引用来源”。做到这一步一个能用的语义搜索 RAG 后端就算立起来了。5. 常见问题与排坑实录我实际项目中踩过的六个坑5.1 相似度结果看起来“全都很高”怎么调阈值很多第一次跑语义搜索的人会惊讶地发现随便什么问题查出来相似度都超过 0.7。这不是 bug而是余弦距离在超高维空间里的特性。当向量维度达到上千时不同向量之间的角度差异会被压缩距离普遍偏小。解决办法是不要拿理论值去卡阈值而是拿一批真实问题跑检索观察正样本的分数区间和负样本的分数区间取一个能明显分隔两者的值。这个过程有点像调分类器阈值需要一些标注数据。5.2 查询很慢但数据量明明不大如果只有几千条数据但查询要几百毫秒甚至几秒问题大概率出在索引没生效。可以用EXPLAIN ANALYZE SELECT ...查看执行计划确认是不是走了Index Scan using documents_embedding_idx。如果没有走索引常见原因有三个一是查询里的距离运算符和索引构建时指定的 ops 不一致比如建索引用的vector_l2_ops查询却用余弦距离二是表中数据量太少优化器觉得全表扫描比索引扫描更快三是查询中嵌套了复杂的过滤条件PostgreSQL 先做了过滤导致无法使用向量索引。第四个原因很隐蔽——如果表里绝大多数行是 NULL 向量索引构建会出现空块实际的召回效率极差。5.3 建 HNSW 索引时内存暴涨甚至 OOMHNSW 构建时需要在内存中维护一张多层图数据量几十万行、向量维度 1536 时内存占用可以达到几个 GB。如果服务器内存紧张建议改用 IVFFlat或者降低hnsw.m最大连接数参数。我遇到过最夸张的情况是容器内存限制 2GB建索引直接被杀进程后来把m从默认的 16 降到 8再配合ef_construction调低一点才在内存范围内构建成功。5.4 同一个向量查询为什么两次结果排序不一样这通常不是 pgvector 的问题而是并行查询导致的非确定性。PostgreSQL 在并行扫描时如果使用了ORDER BY但没有一个唯一的排序键两次查询的结果可能顺序略有差异。措辞严谨的说法是相似度足够接近的向量顺序本来就不稳定。解决方法是排序字段里加上id作为次级排序键ORDER BY embedding [...], id保证结果稳定。5.5 文本切片后内容“驴唇不对马嘴”如果用户问的问题和检索到的 chunk 对不上大概率不是检索 bug而是切分太粗暴。一个典型的场景是一份合同文档只按字面长度切成 500 字一块结果一个块里前半段是甲方义务、后半段是乙方义务语义被切碎了。改进方向有两个一是尽量用有语义边界的切分器比如按 Markdown 段落、按句子二是让检索阶段多返回几块再做一次重排比如先召回 20 块再用重排模型挑最相关的 5 块。重排是目前 RAG 项目提升精度最有效的手段之一代价是多一次模型调用。5.6 混合检索向量检索和关键词检索不要二选一向量检索擅长理解语义相近但字面完全不同的表达比如用户搜“怎么让数据库更快”文档里写的是“性能优化”。但在实体名词匹配合场景关键词检索往往更准比如“pgvector 安装”向量检索可能把“PostgreSQL 索引优化”也拉进来。生产环境建议做“混合检索”向量检索和 PostgreSQL 自带全文检索tsvector各自返回一批结果再用 RRFReciprocal Rank Fusion倒数排名融合合并排序。pgvector 不像专用向量库内置这个能力但 PostgreSQL 原生就有全文检索组合在一起非常自然。-- 同时使用向量相似度 全文检索对结果做简单分数合并 SELECT id, content, ts_rank(to_tsvector(simple, content), plainto_tsquery(simple, 数据库)) AS text_score FROM documents ORDER BY (1 - (embedding [...])) * 0.5 text_score * 0.5 DESC LIMIT 10;这段 SQL 只是混合检索的雏形真实项目可以根据业务调整两类分数的权重甚至用 RRF 把两个独立召回列表的排名融合。原理不复杂但对回答质量提升非常实在。6. 扩展思路pgvector 还能做什么聊完 RAG 这个最热门的场景我想再提几个容易被忽略但没有技术难度的扩展方向。第一个是对比去重。很多内容平台都有“信用同一篇文章洗稿发布”的需求用 pgvector 存文章向量新文章进来先算一下跟已有文章的最大相似度超过阈值直接拦截。这个能力在审核和版权保护里很有价值。第二个是推荐系统。用户行为序列可以映射成向量物品属性也可以映射成向量通过 pgvector 检索“这个用户历史上最像的 N 个物品”做成协同过滤的朴素实现。数据量不大、不需要复杂特征工程时这个方案比上专门推荐系统轻得多。第三个是自然语言生成报表。企业内部有大量维度和指标可以把“指标名称 描述 常用口径”做成一张向量表。用户用自然语言问“上个月华东区销售额”系统先通过语义检索匹配到“销售额总额”这个指标再结合时间、地区条件生成查询 SQL。这一步其实就是把“自然语言 → SQL”的中间层换成了向量检索稳定性和可解释性能提高不少。这些方向的技术底座和前面的语义搜索一模一样只是换了一张表、换了一个业务目标。pgvector 的价值正在于此它不给你的架构增加额外组件却让 PostgreSQL 从一个关系型数据库变成了能理解语义的检索基础设施。从我自己的实践感受来说pgvector 不是那种“性能秒杀专用向量库”的方案但它把工程的复杂度降到了一个很舒适的范围内。尤其当你已经有一套成熟 PostgreSQL 运维体系时用 pgvector 做 RAG 相当于在既有地基上加了一层楼技术风险小、进展快、回报明确。如果你正在纠结要不要引入单独向量库我建议先冷静算一笔账数据量有没有到百万级、过滤条件复杂度高不高、有没有专门的向量数据库运维人力。三个答案都不乐观的话pgvector 大概率就是更适合你的那一个选择。