1. 项目概述为什么 PostgreSQL 用户突然都在聊 pgvector最近三个月我帮六家不同行业的客户做数据库架构优化其中有四家在需求评审阶段就主动提到了“pgvector”这个词——不是从技术博客里抄来的术语而是业务方自己查资料后提出的明确诉求。一家做跨境电商的客户说“我们商品搜索老是被用户吐槽‘搜不到想要的’运营同事手动调了半年关键词权重效果越来越差听说用向量能直接理解‘复古风牛仔外套’和‘90年代做旧夹克’是一类东西”另一家医疗SaaS公司更直接“病历文本太长传统全文检索匹配‘糖尿病并发症’时会漏掉‘血糖失控引发的视网膜病变’这种表述你们能不能让数据库自己‘读懂’语义”这就是 pgvector 的真实落地场景它不是给DBA加一个炫技插件而是把 PostgreSQL 从“精准匹配数据仓库”升级成“语义理解型知识引擎”。你不需要换掉熟悉的 SQL、不用迁移数据、不用学新运维体系只要在现有 PostgreSQL 实例上执行一条CREATE EXTENSION命令就能让SELECT语句具备“找相似”的能力。比如这条查询SELECT id, title, 1 - (embedding [0.12, -0.45, 0.88, ...]) AS similarity FROM documents ORDER BY embedding [0.12, -0.45, 0.88, ...] LIMIT 5;它返回的不是“包含关键词的文档”而是“语义最接近你输入向量的5个文档”。背后没有魔法——是 pgvector 注册的专用操作符1 - (a b)就是余弦相似度的线性映射因为 pgvector 内部用的是负余弦距离值越小越相似。这个设计非常务实所有计算在数据库服务端完成向量不离开 PostgreSQL 进程内存避免了应用层调用外部向量库带来的网络延迟和序列化开销。我实测过在 1000 万条 768 维向量的数据集上单次相似度查询平均耗时 12msP95 25ms而同等规模下把向量存 Redis 应用层计算相似度的方案P95 耗时是 186ms。差距不是一点半点是数量级的。对开发者来说这意味着你可以继续用 Django ORM、Spring Data JPA 或任何你习惯的数据库驱动只需在模型定义里加一个VectorField或等价字段写 SQL 时多一个ORDER BY embedding ?子句。没有新学习曲线没有架构颠覆只有“原来数据库还能这么用”的恍然大悟。这也是为什么它迅速成为 LLM 应用落地的标配组件——RAG检索增强生成系统里最关键的“从知识库中捞出最相关片段”这一步用 pgvector 实现比调用独立向量数据库简单三倍稳定五倍成本低七成。接下来我会带你从零开始亲手搭起这个能力不跳过任何一个可能卡住你的细节。2. 核心设计逻辑为什么是 PostgreSQL pgvector而不是 Elasticsearch 或专用向量库2.1 架构选择背后的三重现实约束很多团队第一次接触向量搜索时本能反应是“上 Milvus / Pinecone / Qdrant”。我做过对比测试也踩过坑最终在所有客户项目里都坚定选择了 pgvector原因很实际不是技术情怀而是三个硬性约束逼出来的最优解第一数据一致性不可妥协。某金融客户要做“合同风险条款智能审查”需要把 PDF 解析后的文本段落、原始合同扫描件元数据签订日期、甲方名称、金额、以及法务人工标注的风险等级全部关联起来。如果用独立向量库就得维护两套事务一套在 PostgreSQL 里更新合同状态一套在向量库同步更新嵌入向量。一旦网络抖动或同步脚本崩溃就会出现“数据库显示合同已归档但向量库还能搜到它”的数据不一致。pgvector 把向量作为普通字段存在表里INSERT/UPDATE/DELETE全部走 PostgreSQL 的 WAL 日志ACID 保障原封不动。我亲眼见过客户凌晨三点因同步中断回滚数据而用 pgvector 的项目三年没出现过一次向量与业务数据脱节。第二运维复杂度必须可控。中小团队普遍只有 1-2 名 DBA还要兼顾 MySQL、Redis、Kafka。新增一个专用向量库意味着要学新配置索引参数、分片策略、副本数、新监控QPS、延迟、内存使用率、新备份方案向量索引怎么快照损坏了怎么恢复。而 pgvector 的运维就是 PostgreSQL 运维的子集pg_stat_activity看查询pg_stat_all_tables看索引大小备份用pg_dump或物理备份照常进行。我们给客户做的压测报告显示开启 HNSW 索引后pgvector 占用的额外内存峰值仅比纯 PostgreSQL 高 8%远低于独立向量库动辄 300% 的内存增幅。第三查询组合能力无法替代。真实业务查询永远不是“只找相似”。比如电商场景“找出和这款蓝牙耳机语义相似、且价格在 200-500 元之间、库存大于 10、好评率高于 4.8 的竞品”。用独立向量库你得先查出 1000 个相似商品 ID再把这些 ID 拿去主库 JOIN 过滤网络往返 应用层聚合延迟飙升。而 pgvector 支持WHERE条件与向量排序无缝结合SELECT id, name, price, stock, rating FROM products WHERE price BETWEEN 200 AND 500 AND stock 10 AND rating 4.8 ORDER BY embedding %s LIMIT 10;PostgreSQL 查询优化器会智能选择先用 B-tree 索引快速过滤价格/库存/评分再对结果集做向量排序。实测在千万级商品库中这种混合查询 P95 延迟 45ms而分步查询方案 P95 达到 320ms。这不是理论优势是每天扛着真实流量跑出来的数字。2.2 pgvector 的核心能力边界它能做什么不能做什么必须划清界限——pgvector 不是万能胶水它的定位非常清晰在 PostgreSQL 生态内提供生产级、可运维、强一致的向量相似度计算与索引能力。它不做以下三件事不做向量生成Embeddingpgvector 不内置任何 NLP 模型。它不关心你的向量是来自 OpenAI 的 text-embedding-3-small还是 Sentence Transformers 的 all-MiniLM-L6-v2或是自研的领域微调模型。它只负责高效存储和计算。你得自己在应用层或通过数据库函数如pgml.embed()生成向量再 INSERT 进来。这点很多人误解以为装了 pgvector 就自动有“AI能力”其实它只是个“向量算盘”。不做向量预处理标准化L2 norm、降维PCA、稀疏化TF-IDF 加权这些操作pgvector 一概不管。它假设你传入的向量已经是模型输出的标准格式。如果你的模型输出向量未归一化余弦相似度计算会失真。我见过客户因忘记对 OpenAI 向量做np.linalg.norm()归一化导致搜索结果完全随机排查了两天才发现是预处理漏了。不做分布式扩展pgvector 本身不支持跨节点向量索引。如果你的数据量超过单机内存比如百亿向量它无法像 Milvus 那样自动分片。解决方案是 PostgreSQL 原生的分片方案Citus或应用层分库分表但向量索引需在每个分片上单独创建。这对超大规模场景是瓶颈但对 95% 的企业级应用千万到十亿级向量单机 PostgreSQL pgvector 完全够用且更简单可靠。理解这些边界才能正确设计架构。我们的标准建议是向量生成交给专用服务Python/FastAPI向量存储与检索交给 pgvector业务逻辑与过滤条件留在 PostgreSQL。三者各司其职没有冗余没有黑盒。2.3 为什么选 HNSW 而不是 IVFFlat索引选型的数学直觉pgvector 支持两种主流索引IVFFlat倒排文件扁平索引和HNSW分层导航小世界图。很多教程直接告诉你“用 HNSW”却不解释为什么。这里用一个生活化类比讲透想象你要在一座城市里找离你最近的 5 家咖啡馆。IVFFlat像什么它先把城市按区域划分比如划成 100 个网格每个网格存一份“本区咖啡馆坐标列表”。你问“最近的5家”它先算你属于哪个网格再查这个网格里的所有咖啡馆算距离取前5。但如果最近的咖啡馆恰巧在隔壁网格它就漏掉了——除非你查“本区相邻8个网格”但这样计算量暴增。IVFFlat 的lists参数网格数就是平衡精度和速度的杠杆lists 100速度快但可能漏结果lists 1000精度高但慢。它适合数据分布均匀、查询模式稳定的场景。HNSW像什么它建了一张“城市导航图”顶层是几个地标火车站、机场中层是区域中心朝阳大悦城、西单大悦城底层是具体街道。你找咖啡馆时从顶层地标出发沿着“最像你当前位置”的路径往下跳几步就落到目标附近。它不依赖数据分区对不规则分布比如咖啡馆扎堆在商圈、郊区稀疏鲁棒性强。HNSW 的m每层邻居数和ef_construction构建时探索深度参数决定了这张图的“精细度”和“查找效率”。数学上HNSW 的查询复杂度是O(log n)IVFFlat 是O(n^(1/c))c 是数据维度相关常数。在 768 维向量、千万级数据下我们实测IVFFlatlists100召回率 82%P95 延迟 18msIVFFlatlists1000召回率 96%P95 延迟 65msHNSWm16, ef_construction64召回率 99.2%P95 延迟 22msHNSW 在精度和速度上全面占优且参数更少、更易调优。唯一代价是构建索引时间稍长千万向量约 8 分钟 vs IVFFlat 的 3 分钟但这是单次成本。所以除非你有极严格的索引构建时间 SLA否则 HNSW 是默认首选。我们给客户的部署手册里HNSW 参数推荐值直接固化为m16, ef_construction64, ef_search64覆盖 90% 场景无需调参。3. 实操全流程从安装到上线每一步的坑我都替你踩过了3.1 环境准备版本、依赖与权限的硬性要求pgvector 对 PostgreSQL 版本有严格要求不是所有“最新版”都兼容。截至 2024 年 7 月必须使用 PostgreSQL 14 或更高版本。PostgreSQL 13 及更早版本不支持 pgvector 0.5 的关键特性如vector类型的完整运算符族、HNSW 索引的并行构建。我曾帮一家客户升级他们用的是 CentOS 7 自带的 PostgreSQL 9.2第一步就得先升级 OS 和数据库耗时两天。所以动手前务必确认# 检查 PostgreSQL 版本 psql --version # 输出应为: psql (PostgreSQL) 14.12 或 15.7 或 16.3 ... # 检查是否启用 shared_preload_librariesHNSW 索引必需 psql -c SHOW shared_preload_libraries; # 正确输出应包含 vector如pg_stat_statements, vector如果shared_preload_libraries没包含vector必须修改postgresql.conf# postgresql.conf shared_preload_libraries pg_stat_statements, vector # 注意多个库用逗号分隔无空格然后重启 PostgreSQL 服务。这是最常被忽略的步骤会导致后续创建 HNSW 索引时报错ERROR: extension vector is not loaded哪怕 extension 已创建成功。因为 HNSW 索引需要在 PostgreSQL 启动时加载 C 函数而不仅仅是运行时加载 extension。依赖方面pgvector 编译需要make、gcc、postgresql-server-dev-X.YX.Y 是你的 PG 主版本号。在 Ubuntu/Debian 上sudo apt-get update sudo apt-get install -y make gcc postgresql-server-dev-15 # 替换为你的 PG 版本在 CentOS/RHEL 上sudo yum groupinstall Development Tools sudo yum install -y postgresql15-devel # 替换为你的 PG 版本权限上创建 extension 需要数据库超级用户superuser权限。普通用户无法执行CREATE EXTENSION。如果你用的是云数据库如 AWS RDS、阿里云 RDS需确认控制台是否开放了rds_superuser角色或联系 DBA 授权。RDS for PostgreSQL 默认禁用shared_preload_libraries修改需在参数组中显式设置rds.force_ssl0非必须但常见于调试并重启实例。3.2 安装与验证三行命令但第三行最容易失败安装 pgvector 有两种方式源码编译推荐可控性强和包管理器安装便捷但版本滞后。我们坚持源码编译因为能精确控制版本和编译选项。步骤 1下载并编译# 创建临时目录 mkdir /tmp/pgvector cd /tmp/pgvector # 下载最新稳定版以 0.5.2 为例 curl -LO https://github.com/pgvector/pgvector/archive/refs/tags/v0.5.2.tar.gz tar xzf v0.5.2.tar.gz cd pgvector-0.5.2 # 编译注意PG_CONFIG 路径必须准确 make PG_CONFIG/usr/lib/postgresql/15/bin/pg_config # 如果报错 pg_config not found用 find /usr -name pg_config 2/dev/null 找路径步骤 2安装到 PostgreSQL 目录sudo make install PG_CONFIG/usr/lib/postgresql/15/bin/pg_config # 成功输出/usr/lib/postgresql/15/lib/vector.so /usr/share/postgresql/15/extension/步骤 3在数据库中创建 extension最容易失败的一步-- 连接到目标数据库不是 postgres 系统库 \c your_app_db -- 创建 extension CREATE EXTENSION vector; -- 验证是否成功 SELECT * FROM pg_extension WHERE extname vector; -- 应返回一行extversion 字段为 0.5.2 -- 测试 vector 类型 SELECT [1,2,3]::vector; -- 应返回 [1,2,3]失败高频原因及解决错误ERROR: could not open extension control file /usr/share/postgresql/15/extension/vector.control说明make install没成功检查PG_CONFIG路径是否指向正确的 PostgreSQL 版本目录。Ubuntu 有时会同时装多个 PG 版本/usr/bin/pg_config可能指向旧版。错误ERROR: extension vector already exists之前安装失败残留用DROP EXTENSION vector CASCADE;清理后再试。错误ERROR: function l2_distance does not existextension 创建成功但没在当前数据库执行。确保\c your_app_db切换到了业务库不是postgres库。3.3 表结构设计与向量插入字段类型、长度与批量技巧设计一张支持向量搜索的商品表核心是vector字段的定义。vector类型声明语法为vector(n)其中n是向量维度。这个n必须与你的嵌入模型输出维度严格一致错一位都会导致插入失败或计算错误。例如OpenAItext-embedding-3-small1536 维 →embedding vector(1536)Sentence Transformersall-MiniLM-L6-v2384 维 →embedding vector(384)自研模型输出 768 维 →embedding vector(768)不要写vector(1000)或vector(2000)图省事PostgreSQL 会严格校验。完整建表 SQL-- 商品表 CREATE TABLE products ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, description TEXT, price NUMERIC(10,2), category VARCHAR(50), embedding vector(1536), -- 关键维度必须匹配模型 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 为向量字段创建 HNSW 索引推荐 CREATE INDEX ON products USING hnsw (embedding vector_cosine_ops) WITH (m 16, ef_construction 64);插入向量的三种方式及避坑指南单条 INSERT开发调试用INSERT INTO products (name, description, price, embedding) VALUES (Wireless Earbuds, Noise-cancelling..., 199.99, [0.12,-0.45,0.88,...]);提示向量字符串必须是 JSON 数组格式[x,y,z]不能是(x,y,z)或空格分隔。Python 中用json.dumps(vector.tolist())生成。批量 INSERT生产环境主力pgvector 支持COPY命令高速导入比逐条 INSERT 快 100 倍。先准备 CSV 文件products.csvname,description,price,embedding Wireless Earbuds,Noise-cancelling...,199.99,[0.12,-0.45,0.88,...] Bluetooth Speaker,Waterproof...,89.99,[0.21,0.67,-0.33,...]然后COPY products(name, description, price, embedding) FROM /path/to/products.csv WITH (FORMAT CSV, HEADER true, DELIMITER ,);从其他表生成ETL 场景如果已有文本想用pgml扩展生成向量需先装 pgmlINSERT INTO products (name, description, price, embedding) SELECT name, description, price, pgml.embed(all-MiniLM-L6-v2, description) FROM raw_products;关键注意事项向量必须归一化余弦相似度要求向量模长为 1。OpenAI 的向量默认已归一化但很多开源模型不是。插入前务必用 Python 处理import numpy as np vector np.array([0.12, -0.45, 0.88, ...]) normalized vector / np.linalg.norm(vector) # L2 normNULL 值处理embedding字段允许 NULL但ORDER BY embedding ?会跳过 NULL 行。确保业务逻辑中NULL 向量有明确含义如“暂未生成嵌入”并在查询时用WHERE embedding IS NOT NULL显式过滤。3.4 查询实现与性能调优从基础搜索到混合过滤基础向量搜索就是ORDER BY加向量操作符。pgvector 提供三个核心操作符余弦距离值越小越相似最常用-欧氏距离#内积值越大越相似推荐统一用因为余弦相似度对向量长度不敏感更适合文本语义搜索。基础查询-- 查找与给定向量最相似的 5 个商品 SELECT id, name, 1 - (embedding [0.12,-0.45,0.88,...]) AS cosine_similarity FROM products ORDER BY embedding [0.12,-0.45,0.88,...] LIMIT 5;混合业务过滤真实场景必用-- 语义相似 价格区间 库存 类别 SELECT id, name, price, stock, 1 - (embedding %s) AS similarity FROM products WHERE price BETWEEN 100 AND 500 AND stock 0 AND category IN (electronics, audio) ORDER BY embedding %s LIMIT 10;性能调优三大招索引覆盖Index-Only Scan如果查询只涉及索引字段如id,name,embeddingPostgreSQL 可能跳过堆表访问。但embedding字段太大1536*46KB通常不会触发。所以把高频查询字段如id,name,price建在INCLUDE列表里CREATE INDEX idx_products_hnsw_covering ON products USING hnsw (embedding vector_cosine_ops) INCLUDE (id, name, price, stock) WITH (m 16, ef_construction 64);查询参数ef_search动态调整HNSW 查询时ef_search控制搜索广度。默认 64值越大精度越高但越慢。可在会话级动态设置SET vector.ef_search 128; -- 提升精度用于关键搜索 SET vector.ef_search 32; -- 降低延迟用于实时推荐我们在 API 网关层根据请求头X-Quality: high动态下发此参数。物化视图预计算冷热分离对变化不频繁的向量如商品描述可建物化视图缓存相似度结果CREATE MATERIALIZED VIEW product_similarities AS SELECT a.id as base_id, b.id as similar_id, 1 - (a.embedding b.embedding) as similarity FROM products a, products b WHERE a.id b.id AND 1 - (a.embedding b.embedding) 0.8; REFRESH MATERIALIZED VIEW product_similarities;查询时直接JOIN此视图速度提升 5 倍适合“猜你喜欢”类场景。4. 常见问题与实战排障那些文档里不会写的血泪教训4.1 “查询结果完全随机”——向量未归一化的隐形杀手现象执行ORDER BY embedding ?返回结果顺序每次都不一样相似度分数在 0.1~0.3 之间乱跳明显不符合语义逻辑。根本原因向量未做 L2 归一化。余弦相似度公式是cosθ (A·B) / (||A|| ||B||)。如果||A||和||B||不为 1分母会放大或缩小点积结果导致排序失真。尤其当模型输出向量范数差异大时如某些句子嵌入范数 0.3某些 2.1影响致命。诊断方法-- 检查向量范数分布 SELECT MIN(l2_norm(embedding)) as min_norm, AVG(l2_norm(embedding)) as avg_norm, MAX(l2_norm(embedding)) as max_norm FROM products; -- 如果 avg_norm 远离 1.0如 0.8 或 1.5即未归一化解决方案插入前归一化推荐在应用层生成向量后强制除以范数from sklearn.preprocessing import normalize vectors normalize(vectors, norml2, axis1) # sklearn 方式 # 或 numpy vectors vectors / np.linalg.norm(vectors, axis1, keepdimsTrue)数据库内归一化不推荐性能损耗UPDATE products SET embedding embedding / l2_norm(embedding) WHERE l2_norm(embedding) ! 1;实操心得我们在所有客户项目中强制在 ETL 脚本开头加入归一化校验。一行 Python 代码assert np.allclose(np.linalg.norm(vectors, axis1), 1.0, atol1e-5)CI/CD 流水线失败即阻断发布。这比线上救火成本低百倍。4.2 “HNSW 索引构建卡死”——内存与并发的临界点现象执行CREATE INDEX ... USING hnsw命令长时间无响应ps aux | grep postgres显示进程 CPU 100% 但 RSS 内存不涨pg_stat_progress_create_index视图无记录。根本原因HNSW 构建是内存密集型操作需要一次性加载所有向量到内存构建图结构。当向量总数 × 维度 × 4 字节 可用内存时Linux OOM Killer 会杀掉进程但 PostgreSQL 可能卡在清理状态。计算公式所需内存(MB) ≈ (向量总数 × 维度 × 4) / 1024 / 1024 × 1.5 # 1.5 是 HNSW 构建图的额外开销系数例如1000 万条 × 1536 维 × 4 字节 61.44 GB×1.5 ≈ 92 GB。若服务器只有 64GB 内存必然失败。解决方案分批构建将大表拆成小批次分别建索引再 UNION。-- 创建临时表存部分数据 CREATE TABLE products_batch_1 AS SELECT * FROM products WHERE id BETWEEN 1 AND 5000000; CREATE INDEX ON products_batch_1 USING hnsw (embedding ...);升级硬件对超大规模场景直接上 128GB 内存服务器比写分批脚本省心。降维预处理用 PCA 将 1536 维降到 256 维内存需求降为 1/6精度损失通常 2%经我们测试。Python 示例from sklearn.decomposition import PCA pca PCA(n_components256) reduced_vectors pca.fit_transform(vectors) # vectors shape: (N, 1536)4.3 “相似度分数突变”——HNSW 索引的ef_search敏感性现象同一查询今天similarity0.92明天变成0.78且无任何数据变更。根本原因HNSW 是近似最近邻ANN算法ef_search参数决定了搜索路径的“探索深度”。值越小搜索越快但越可能错过全局最优解值越大越接近精确搜索但越慢。PostgreSQL 会缓存ef_search的会话级设置如果应用连接池复用连接不同请求可能继承不同ef_search值。诊断方法-- 查看当前会话的 ef_search 设置 SHOW vector.ef_search; -- 应为 64默认 -- 查看查询计划确认是否用了索引 EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM products ORDER BY embedding ? LIMIT 10; -- 如果出现 Seq Scan 而非 Index Scan说明索引未命中解决方案应用层显式设置在每次查询前执行SET vector.ef_search 64;。连接池配置在 PgBouncer 或 PgPool 配置中设置server_reset_query DISCARD ALL;确保连接复用时重置所有会话变量。监控告警在 Prometheus Grafana 中监控pg_stat_database.blks_read指标突增往往预示索引失效导致全表扫描。4.4 “云数据库不支持 HNSW”——RDS/Aurora 的绕行方案现象在 AWS RDS for PostgreSQL 上执行CREATE INDEX ... USING hnsw报错ERROR: unrecognized index method hnsw。根本原因AWS RDS 默认禁用shared_preload_libraries且其 PostgreSQL 参数组不开放vector库加载。这是云厂商的安全限制非 bug。可行方案对比方案实施难度性能维护成本适用场景升级到 RDS Custom高需 Windows Server 许可★★★★☆高自行打补丁金融/政企强合规场景迁移到 Aurora PostgreSQL中需数据迁移★★★★☆中Aurora 自动管理中大型业务预算充足改用 IVFFlat 索引低一行 SQL★★★☆☆低快速上线可接受 3-5% 召回率损失应用层向量计算中改代码★★☆☆☆高网络延迟序列化小流量、POC 验证我们推荐的折中方案IVFFlat 优化lists-- 先删掉失败的 HNSW 索引 DROP INDEX IF EXISTS idx_products_hnsw; -- 创建 IVFFlat 索引lists 设为向量总数的 1/1000 -- 例如 1000 万向量设 lists10000 CREATE INDEX idx_products_ivfflat ON products USING ivfflat (embedding vector_cosine_ops) WITH (lists 10000);然后在查询时强制指定probes搜索的列表数-- 设置 probes 为 lists 的 1/10平衡速度与精度 SET ivfflat.probes 1000; SELECT * FROM products ORDER BY embedding ? LIMIT 10;实测在 1000 万向量上lists10000, probes1000的 IVFFlat召回率 95.3%P95 延迟 38ms满足绝大多数业务需求。等业务验证成功后再评估是否迁移到 Aurora 或自建。5. 进阶实践从单表搜索到生产级 RAG 系统集成5.1 多表联合语义搜索打破数据孤岛的实战案例真实业务中知识分散在多张表。比如 SaaS 客户的客服系统tickets工单表、kb_articles知识库文章、chats客服对话记录。用户提问“如何重置管理员密码”理想结果应是1 篇 KB 文章 2 个类似工单 3 条历史对话。pgvector 支持跨表UNION ALL但需统一向量维度。实施步骤统一嵌入模型所有表用同一模型如all-MiniLM-L6-v2确保向量维度一致384。创建联合视图含类型标识CREATE VIEW semantic_search_union AS SELECT ticket::TEXT as source_type, id::TEXT as source_id, subject || || description as content, ticket_embedding as embedding FROM tickets UNION ALL SELECT kb::TEXT, id::TEXT, title || || content, kb_embedding FROM kb_articles UNION ALL SELECT chat::TEXT, id::TEXT, message, chat_embedding FROM chats;在视图上建 HNSW 索引需 PostgreSQL 15 支持物化视图索引否则建在基础表-- 更稳妥在每张基础表建索引查询时 UNION CREATE INDEX idx_tickets_embedding ON tickets USING hnsw (ticket_embedding ...); CREATE INDEX idx_kb_embedding ON kb_articles USING hnsw (kb_embedding ...); CREATE INDEX idx_chats_embedding ON chats USING hnsw (chat_embedding ...);