编程 pgvector 深度拆解:当关系数据库学会「向量检索」——HNSW/IVFFlat 索引原理、PostgreSQL 18 AIO 加持与 RAG 工程实战

2026-07-16 01:17:53 +0800 CST views 9

pgvector 深度拆解:当关系数据库学会「向量检索」——HNSW/IVFFlat 索引原理、PostgreSQL 18 AIO 加持与 RAG 工程实战

2026 年,几乎每个后端团队都在问同一个问题:"我的 RAG / 语义搜索 / 推荐系统,到底要不要再引入一个独立的向量数据库?" 网上充斥着《PostgreSQL 18 原生向量搜索真香》这样的标题,但真相是:PostgreSQL 18 的正式版并没有把向量计算"下沉到内核"。所谓"原生向量搜索",到今天依然靠 pgvector 这个扩展撑着。本文不蹭热点、不制造焦虑,从工程视角把 pgvector 的底层原理、PostgreSQL 18 到底帮了什么忙、以及一条能上生产的 RAG 链路,彻底讲透。读完你会对"该不该上专用向量库"有自己的判断,而不是被标题党带着跑。


一、背景:为什么向量检索成了数据库的"必修课"

过去十年,数据库的战场是"结构化数据":行、列、索引、事务、JOIN。但大模型时代改变了游戏规则。

当你把一段文本、一张图片、一段语音丢进 Embedding 模型(OpenAI text-embedding、BGE、E5、bge-m3……),它会输出一个 高维浮点向量(常见 384 / 768 / 1536 / 3072 维)。语义相近的内容,在向量空间里距离更近。于是"搜索"这件事从 WHERE title LIKE '%关键词%' 变成了"找出和这个向量最近的 K 个向量"——最近邻搜索(Nearest Neighbor Search)

典型场景:

  • RAG(检索增强生成):先把知识库切片 Embedding 入库,用户提问时检索最相关片段,拼进 Prompt 喂给 LLM。
  • 语义搜索:用户搜"怎么给猫驱虫",能召回"幼猫体内寄生虫防治方案",而不是死磕关键词匹配。
  • 推荐 / 去重 / 以图搜图:商品、文章、人脸,本质都是向量相似度问题。

2023 年之前,这类需求的标准答案是独立的向量数据库:Milvus、Qdrant、Weaviate、Pinecone。但引入它们意味着:多一套存储、多一套运维、多一个数据同步链路(业务数据改了,向量什么时候同步?最终一致还是强一致?),以及"我的用户表和向量表怎么 JOIN"的灵魂拷问。

pgvector 的出现,让"用我已经在用的 PostgreSQL 顺手把向量也存了"变成现实。它不是一个独立系统,而是 PostgreSQL 的一个扩展(extension)——和 postgispg_stat_statements 一个性质。这一点必须先讲清楚,因为它决定了后面所有的架构判断。

关键澄清:很多文章说"PostgreSQL 18 把向量搜索做进内核了",这是不准确的。截至 PG18,向量类型与索引依然由 pgvector 扩展提供,核心代码里没有原生的 vector 类型。PG18 真正给向量检索"助攻"的,是底层 I/O 与查询优化器的一系列改进(见第四节)。把"扩展能力"说成"内核原生",是典型的标题党。

把视角拉回 2026 年:'关系数据库 + 向量'的融合早已不是 pgvector 一个人在战斗。Oracle、SQL Server、Redis(Query Engine 的向量索引)、ClickHouse、Elasticsearch 8 的 dense_vector 都在抢同一块蛋糕——行业的共识是:向量检索正在从"独立中间件"降级为"数据库的又一个一等公民能力"。pgvector 的特别之处,在于它证明了"用扩展而非改内核"这条路在 PostgreSQL 上完全走得通:既享受社区的高速迭代,又不破坏核心的稳定性契约。这恰恰解释了为什么它成了中小团队落地 AI 检索的默认入口。


二、核心概念:向量、距离与"近似"的必要性

2.1 向量与距离度量

pgvector 的核心是 vector 数据类型。它存的就是一组浮点数:

-- 安装扩展(每个数据库只需一次)
CREATE EXTENSION IF NOT EXISTS vector;

-- 建一张带向量列的表,1536 维对应 OpenAI text-embedding-3-small
CREATE TABLE documents (
    id          BIGSERIAL PRIMARY KEY,
    content     TEXT        NOT NULL,
    embedding   vector(1536),
    metadata    JSONB,
    created_at  TIMESTAMPTZ DEFAULT now()
);

pgvector 提供三类距离算子,选哪个取决于你的 Embedding 模型怎么训练的:

算子距离含义适用场景
<->L2(欧氏距离)向量空间中的直线距离通用,未归一化向量常用
<=>余弦距离1 - 余弦相似度,衡量方向而非长度最常用,语义相似度
<#>内积(负)向量点积取负(pgvector 用负值便于 ORDER BY ... ASC已归一化且需考虑模长时

工程经验:绝大多数语义检索用余弦距离。如果你的 Embedding 已经归一化(openai 的默认输出就是单位向量),1 - (a <=> b) 就是余弦相似度。千万别混用——用 L2 去比较本该用余弦的向量,效果会莫名其妙地差。

除了算子,pgvector 也提供函数式写法(l2_distance(a,b)cosine_distance(a,b)inner_product(a,b)),在复杂 SQL 里可读性更好。

2.2 精确 vs 近似:为什么不能全表扫

最朴素的检索就是暴力计算:拿查询向量和库里每一个向量算距离,排序取 Top-K。复杂度 O(N × D),N 是向量条数,D 是维度。

  • 10 万条 1536 维向量,每次查询要算 1.5 亿次乘加。还能忍。
  • 1000 万条?每次查询 1500 亿次浮点运算,延迟直接崩到几百毫秒甚至秒级。

真实业务里向量规模轻易到千万、亿级。暴力搜索不可行。近似最近邻(ANN, Approximate Nearest Neighbor) 因此成为标配:牺牲一点点召回率(能不能找回全部真正最近的 K 个),换来数量级的延迟下降。pgvector 提供两种 ANN 索引:IVFFlatHNSW。理解它们的差异,是你能否把向量检索调快的决定性因素。


三、架构分析:pgvector 的两种索引到底怎么干活

3.1 IVFFlat:倒排文件,快建但"看运气"

IVFFlat(Inverted File Flat)的思想像图书馆分类上架:

  1. 训练阶段:用聚类算法(类似 k-means)把所有向量分成 lists 个"书架"(聚类中心)。
  2. 入库阶段:每条向量挂到离它最近的那个聚类中心下面。
  3. 查询阶段:算出查询向量最近的 probes 个聚类中心,只在这几个"书架"里做精确比对。
-- 先有数据再建索引(IVFFlat 需要训练,空表建不了)
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

-- 查询时扩大探查的聚类数,召回率↑但延迟↑
SET ivfflat.probes = 10;

优点:构建快、占内存小、实现简单。
缺点:召回率受 probes 影响大;如果查询向量恰好落在聚类边界,"最相似的那个"可能在你没探查的书架上,直接漏掉。数据分布不均匀时(实际业务几乎一定不均匀),效果很看脸。

经验法则:listssqrt(行数) 左右;probes 先取到 lists 的 5%~10%,再按召回率调。

3.2 HNSW:分层导航小世界,慢建但又快又准

HNSW(Hierarchical Navigable Small World,分层可导航小世界)是今天的事实标准。它构建一张多层图

  • 顶层:节点极稀疏,像城市主干道地图,快速把搜索定位到大区域。
  • 中层:节点变密,像街道地图,缩小范围。
  • 底层:包含所有节点,像详细地图,精确定位到目标向量。

每个节点连向它最相似的 m 个邻居。搜索时从顶层出发,沿着边"跳"到更近的邻居,逐层下钻,几步就逼近目标——这就是"小世界":任意两点之间只需很少的跳跃就能连通(六度空间理论的图论版)。

-- m=16 每个节点连 16 个邻居;ef_construction=64 建图时每层考察的候选数
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

关键参数:

  • m:每层每个节点的邻居数。越大召回率越高、图越密、内存越大、构建越慢。默认 16,质量敏感场景可到 32~64。
  • ef_construction:建索引时每层从候选里挑邻居的视野宽度。越大索引质量越高、构建越慢。默认 64,追求质量可 128~200。
  • hnsw.ef_search(GUC,查询期生效,默认 40):查询时考察的候选宽度。这是你线上调召回率最直接的旋钮——不用重建索引。
-- 线上想提高召回率,直接调 GUC,零成本
SET hnsw.ef_search = 200;
SELECT id, content, 1 - (embedding <=> %s) AS similarity
FROM documents
ORDER BY embedding <=> %s
LIMIT 5;

HNSW 的最大优势:空表就能建索引,数据动态增删不影响结构,查询延迟稳定在毫秒级,召回率可以做到很高。代价是构建慢、占内存(整张图要能放内存里才快)、磁盘占用大。

HNSW 到底是怎么把图建出来的? 插入一个新向量时,算法先从顶层入口找到离它最近的若干邻居,逐层下钻到最底层,记录每层走过的路径;然后以概率 1/m 决定这个节点是否"晋升"到上一层(越往上节点越稀疏,这正是一个指数衰减的层高分布,保证顶层是稀疏的"主干道")。在它被分配到的每一层里,把新节点连向那一层最近的 m 个邻居,同时可能反向让那些邻居也连回它(受每层最大连接数约束)。查询时同理:从顶层入口出发,贪心地向更近的邻居移动,到达局部最优后下钻到下一层,直到底层输出候选,最后用 ef_search 控制最终候选宽度做精排。理解了"晋升概率 1/m"和"每层次大 m 个邻居",你就明白为什么 m 调大图更密、召回更高——本质是给每个节点铺了一张更大的"社交关系网",搜索时能走的近道更多。

3.3 两者怎么选(决策表)

维度IVFFlatHNSW
构建速度慢(可能数小时/千万级)
内存占用高(图常驻内存)
查询延迟低且稳定
召回率中(依赖 probes)高(可微调 ef_search)
能否空表建否(需先有数据训练)
动态写入重建代价小友好
适用静态归档、低频更新、省资源实时检索、RAG、推荐

一句话结论:新项目无脑上 HNSW;只有数据极静态、资源极紧张、且能接受偶尔漏召回时,才考虑 IVFFlat。

3.4 pgvector 的数据类型全家桶

vector 只是基础。pgvector 还提供:

  • halfvec:半精度浮点向量,最多 4000 维,内存直接砍半,精度损失在检索场景通常可忽略——省内存神器。
  • sparsevec:稀疏向量,适合 TF-IDF、BM25 这类"大多数维度为 0"的场景(混合检索里很有用)。
  • bit:二进制向量,做海明距离,适合某些哈希类特征。
-- 用半精度把 1536 维的存储和内存都压下来
ALTER TABLE documents ALTER COLUMN embedding TYPE halfvec(1536);

3.5 执行计划:PostgreSQL 到底走不走 HNSW 索引

很多新手会困惑:"我建了 HNSW 索引,为什么 EXPLAIN 里还是 Seq Scan?"答案是:索引用不用,由优化器按 cost 决定。当表里只有几百条向量时,全表扫一遍算距离可能比"走索引 + 回表"还便宜,优化器就会放弃索引。只有数据量上来(通常几万条以上),HNSW 的 cost 优势才压过顺序扫描。

-- 看 PG 到底怎么检索的
EXPLAIN ANALYZE
SELECT id, content, 1 - (embedding <=> %s) AS similarity
FROM kb_chunks
ORDER BY embedding <=> %s
LIMIT 5;
-- 大表应看到:Index Scan using kb_chunks_embedding_idx ... (HNSW)
-- 小表可能是:Seq Scan ... 然后 Sort,属正常现象

-- 调试时可强制走索引,验证索引本身没问题
SET enable_seqscan = off;

注意 hnsw.ef_search执行期旋钮,不影响优化器的索引选择,只影响索引扫描时的候选视野宽度。ivfflat.probes 同理。理解这一点,你就不会在"索引明明建了却没生效"上浪费时间——要么数据量还不够,要么 cost 估算被统计信息误导(跑一次 ANALYZE kb_chunks 通常就好)。


四、PostgreSQL 18 到底给向量检索帮了什么忙

回到开头的澄清:PG18 没有原生向量类型。但 PG18 确实有几个底层改进,对"向量表越来越大"的检索场景是实打实的利好。

4.1 异步 I/O(AIO)子系统

PG18 引入了全新的异步 I/O 框架,Linux 上基于 io_uring。过去顺序扫描、位图堆扫描、VACUUM 的读是同步阻塞的——CPU 干等磁盘。现在可以一次性并发发起多个读请求,CPU 边处理边等数据回来。

为什么和向量检索有关?当你用 WHERE metadata->>'lang'='zh' 做过滤、再 ORDER BY embedding <=> q 时,PG 往往要先扫一批行、做过滤、再算距离。扫行阶段的随机/顺序读被 AIO 加速,整体延迟下降。官方基准在云存储等 I/O 受限场景下,读密集查询有 2~3 倍提升。

-- PG18 配置异步 I/O 方式,Linux 推荐 io_uring
ALTER SYSTEM SET io_method = 'io_uring';
-- 或 'worker'(通用,跨平台)、'sync'(回退)

4.2 Skip Scan:多列索引不再被"最左前缀"卡死

过去一个 (lang, created_at) 的多列 B-tree 索引,如果你的查询只过滤 created_at 不带 lang,索引基本废了。PG18 的 Skip Scan 让"跳过前导列"也能高效利用索引。对你的向量表做结构化过滤(先按业务字段缩小范围,再做向量检索)是大杀器——它让"混合检索"的前置过滤快很多。

4.3 RETURNING 增强(OLD / NEW)

PG18 让 INSERT/UPDATE/DELETE/MERGERETURNING 能同时拿到修改前(OLD)和修改后(NEW)的行。在向量场景里,更新一条文档的 embedding 后能直接返回新旧向量,做缓存失效、审计、ETL 都省一次往返。

4.4 虚拟生成列、UUIDv7、OAuth2 认证、EXPLAIN 升级

虚拟生成列可以自动派生 metadata 里的某个字段做索引;UUIDv7 带时间戳前缀,做主键插入更友好(避免 UUIDv4 随机主键的索引膨胀);OAuth2 认证让托管 PG 的接入更安全;EXPLAIN 的升级让你能更清楚地看到"这次查询到底走没走 HNSW 索引"。

这些都和向量检索"间接相关":向量表也是业务表,业务表的那些老问题(主键、过滤、审计、认证)一个都不少。用 pgvector 的最大价值,正是向量和这些业务属性在同一张表里、同一套事务、同一个 SQL 里统一解决


五、代码实战:从零搭一条生产级 RAG 链路

光讲原理不够,下面用 Python + pgvector 把"知识库 → 检索 → 生成"完整跑通。

5.1 起一个带 pgvector 的 PostgreSQL

# 官方镜像已带 pgvector 扩展(14+ 的各类发行版)
docker run -d --name pg-vector \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=rag \
  -p 5432:5432 \
  pgvector/pgvector:pg18

pip install openai psycopg2-binary pgvector

5.2 建表与索引

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE kb_chunks (
    id         BIGSERIAL PRIMARY KEY,
    doc_id     BIGINT      NOT NULL,
    chunk_idx  INT         NOT NULL,
    content    TEXT        NOT NULL,
    embedding  vector(1536),
    lang       TEXT        DEFAULT 'zh',
    created_at TIMESTAMPTZ DEFAULT now()
);

-- HNSW 余弦索引;metadata 过滤靠单独的 B-tree/GIN 索引加速
CREATE INDEX ON kb_chunks USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
CREATE INDEX ON kb_chunks (lang);
CREATE INDEX ON kb_chunks (doc_id);

5.3 Embedding 与写入

import openai
from pgvector.psycopg2 import register_vector
import psycopg2

conn = psycopg2.connect("postgres://postgres:secret@localhost:5432/rag")
register_vector(conn)            # 让 psycopg2 认识 vector 类型
cur = conn.cursor()

client = openai.OpenAI()

def embed(text: str) -> list[float]:
    r = client.embeddings.create(
        model="text-embedding-3-small",   # 1536 维
        input=text,
    )
    return r.data[0].embedding

# 假设已经把一篇长文切成若干 chunk
chunks = [
    "PostgreSQL 18 引入了异步 I/O 子系统,基于 io_uring……",
    "pgvector 提供 HNSW 与 IVFFlat 两类近似最近邻索引……",
    # ... 更多 chunk
]

for i, text in enumerate(chunks):
    cur.execute(
        "INSERT INTO kb_chunks (doc_id, chunk_idx, content, embedding, lang) "
        "VALUES (%s, %s, %s, %s, %s)",
        (1, i, text, embed(text), "zh"),
    )
conn.commit()

5.4 相似度检索(带结构化过滤)

query = "PostgreSQL 18 对向量检索有什么性能提升?"
q_vec = embed(query)

# 只在中文库里检索,余弦相似度降序取前 5
cur.execute(
    """
    SELECT id, content,
           1 - (embedding <=> %s) AS similarity
    FROM kb_chunks
    WHERE lang = 'zh'
    ORDER BY embedding <=> %s
    LIMIT 5
    """,
    (q_vec, q_vec),
)

for row in cur.fetchall():
    print(f"[{row[2]:.3f}] {row[1][:60]}")

过滤检索的坑(必看):pgvector 的 HNSW 是"后过滤"——它先在向量索引里找候选,再应用 WHERE lang='zh'。如果中文只占 1%,你可能凑不够 5 条。解法有二:(1) 提高 hnsw.ef_search 让候选池更大;(2) 用子查询先按结构化条件缩表再算向量(数据量不大时更直接):

SELECT id, content, 1 - (embedding <=> %s) AS similarity
FROM (SELECT * FROM kb_chunks WHERE lang = 'zh') t
ORDER BY embedding <=> %s
LIMIT 5;

5.5 拼成完整 RAG

def retrieve(query: str, k: int = 5) -> list[str]:
    q_vec = embed(query)
    cur.execute(
        "SELECT content FROM kb_chunks ORDER BY embedding <=> %s LIMIT %s",
        (q_vec, k),
    )
    return [r[0] for r in cur.fetchall()]

def generate(query: str) -> str:
    context = "\n\n".join(retrieve(query))
    prompt = f"基于以下资料回答问题,不要编造:\n\n{context}\n\n问题:{query}"
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
    )
    return r.choices[0].message.content

print(generate("PostgreSQL 18 对向量检索有什么性能提升?"))

到这里,一条"切片 → Embedding → pgvector 存储 → 余弦检索 → 拼 Prompt → LLM 生成"的 RAG 链路就闭环了。全程没有引入任何独立向量数据库,向量和业务数据在同一个事务里保持一致。


六、性能优化:让它真的快、真的省

6.1 索引选型决策树

数据是否频繁动态增删? ──是──> HNSW
        │否
是否极度省内存 / 低频查询? ──是──> IVFFlat
        │否
                └──> HNSW(默认无脑选)

6.2 HNSW 调参清单

目标调什么怎么调
提高召回率(不改索引)hnsw.ef_search40 → 100 → 200,观测延迟
提高索引质量(可接受慢建)ef_construction64 → 128 → 200
提高图密度/质量m16 → 32,内存随之翻倍
加速建索引max_parallel_maintenance_workers设大,HNSW 支持并行建
控制内存改用 halfvec1536 维内存直接减半
-- 加速 HNSW 并行构建
SET max_parallel_maintenance_workers = 8;
CREATE INDEX CONCURRENTLY ON kb_chunks
USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 128);

小技巧:CREATE INDEX CONCURRENTLY 不会锁表,生产环境加向量索引务必加这个,否则大表建索引会把写入全堵死。

6.3 维度别贪高:Matryoshka 截断

pgvector 对建索引的维度有上限(历史上约 2000 维)。而 OpenAI 的 text-embedding-3 系列支持 Matryoshka 表示——可以在不重训的情况下把 1536 维截断到 512、256 维,且检索质量损失极小。维度砍半,内存、索引、查询延迟全降。

# 截断到前 512 维,既满足索引限制又省资源
vec = embed(text)[:512]

6.4 召回率怎么量化

别拍脑袋调参,用带标签的查询集算召回率:

def recall_at_k(ground_truth: set, retrieved: list, k: int) -> float:
    topk = set(retrieved[:k])
    return len(topk & ground_truth) / len(ground_truth)

# 提升 ef_search 后重测,找到召回率/延迟的甜点

6.5 和专用向量数据库的选型对比

维度pgvector(PG 扩展)Milvus / Qdrant / Pinecone
数据一致性ACID,强一致多为最终一致
SQL / JOIN原生支持需额外查询层
运维复杂度复用现有 PG独立集群,较重
超大规模(十亿+)吃力(图进内存)擅长,分布式
GPU 加速有(Milvus/Qdrant)
强过滤检索需技巧(见 5.4)原生支持(metadata 索引)
成本低(无新增组件)中高

选型口诀

  • 向量量 < 千万级、要和业务数据强一致、想少运维 → pgvector 直接上
  • 向量量 > 亿级、要 GPU、要复杂 metadata 过滤、做独立检索服务 → 上专用向量库,业务库和向量库解耦。
  • 中间地带:pgvector 先顶着,真到瓶颈再拆。拆的时候,DuckDB 之类做离线分析、pgvector 做在线检索的混合架构也很常见。

6.6 常见坑

  1. 维度超过索引上限:报错或建不了索引,用 Matryoshka 截断(6.3)。
  2. 索引膨胀 / 召回下降VACUUM 不及时、频繁 UPDATE embedding 会让 HNSW 质量劣化,定期 REINDEX CONCURRENTLY
  3. 忘记 CONCURRENTLY:大表建索引锁全表,线上事故高发。
  4. 余弦/L2 混用:Embedding 没归一化却用余弦,效果差。先确认模型的归一化约定。
  5. 过滤后凑不够 K 条:见 5.4 的子查询解法或调大 ef_search

6.7 生产部署 checklist

向量表也是业务大表,生产环境躲不开经典 PG 运维题:

  • 连接池必上 PgBouncer / pgcat:PostgreSQL 是"一连接一进程",向量应用往往长连接 + 高并发,直接裸连会拖垮连接数。用事务级(transaction)池化;若用 PREPARE 语句要注意事务池下跨事务不可用。
  • 读扩展靠只读副本:流复制会把 HNSW 索引自动同步到副本。检索是读多写少的场景,把 Top-K 查询打到只读副本,主库只负责写入和 REINDEX
  • 分区降低单表规模:按 tenant_id 或时间 RANGE 分区,每个子表控制在千万级内,REINDEX CONCURRENTLY 和维护窗口都更轻。
  • 让热点图待在内存:HNSW 图常驻内存才快。调大 shared_buffers,并用 pg_prewarm 把向量索引预热进缓存,避免冷启动查询毛刺。
  • 监控两板斧pg_stat_statements 抓慢的向量查询;EXPLAIN ANALYZE 验证是否走了索引、扫了多少候选。
  • 备份一致性天然:向量列和普通列在同一个库、同一份 WAL 里,pg_dump / 流复制天然一致,不用操心"业务库和向量库对不上"这种分布式系统的经典痛点。

6.8 顺手做个基准,别凭感觉

上线前用真实数据压一下,暴力 vs HNSW 的差距会让你安心:

import time, random
q = [random.random() for _ in range(1536)]

cur.execute("SET enable_indexscan = on;")
t0 = time.time()
cur.execute("SELECT id FROM kb_chunks ORDER BY embedding <=> %s LIMIT 10", (q,))
cur.fetchall()
print(f"HNSW 延迟: {(time.time()-t0)*1000:.1f} ms")

cur.execute("SET enable_indexscan = off; SET enable_seqscan = on;")
t0 = time.time()
cur.execute("SELECT id FROM kb_chunks ORDER BY embedding <=> %s LIMIT 10", (q,))
cur.fetchall()
print(f"暴力扫描延迟: {(time.time()-t0)*1000:.1f} ms")
# 百万级数据上,两者往往差出 100~1000 倍

七、总结与展望

pgvector 的价值,从来不是"它比专用向量库更快"——在十亿级、GPU、复杂过滤这些硬指标上它确实打不过 Milvus/Qdrant。它的价值在于**"少一个系统"**:向量和业务数据在同一个 PostgreSQL 里,同一套事务、同一个 SQL、同一份备份、同一套运维。对绝大多数团队(向量量千万级以内、要强一致、不想养另一个数据库),这就是最优解。

从 roadmap 看,pgvector 0.7 已经抬高了可索引维度上限,并引入了更聪明的量化与并行构建;社区也在讨论让 HNSW 支持"带谓词过滤的检索"(filtered / search-with-predicate),以根治 5.4 里提到的后过滤漏数据问题。一旦这些落地,pgvector 在"强过滤检索"这个最后的软肋上也会被补上。至于内核是否收编向量类型,短期看概率不高——扩展模式让 pgvector 能按自己的节奏发版,不必等 PostgreSQL 一年一度的主版本节奏。换言之,"用扩展做到接近原生的体验"这条路,pgvector 已经走通了。

同时请记住那个被标题党掩盖的事实:PostgreSQL 18 没有把向量做进内核,pgvector 依旧是扩展。但 PG18 的异步 I/O、Skip Scan、RETURNING 增强,确实让"向量表也是业务大表"这件事更顺了。理解这一点,你就不会被《原生向量搜索真香》带节奏,也不会在选型时盲目跟风。

未来值得关注:pgvector 社区在持续逼近"准原生"体验——更大的可索引维度、更好的过滤检索、更聪明的量化。而 PostgreSQL 内核是否终有一天把向量类型收编,取决于社区对"关系 + 向量"融合路线的投票。在那之前,pgvector 就是你在 PostgreSQL 里做 AI 检索最务实的一把刀

工程建议清单(抄走即用):

  1. 新项目向量检索无脑 pgvector + HNSW(cosine)
  2. 建索引永远 CREATE INDEX CONCURRENTLY
  3. 线上调召回率先动 hnsw.ef_search,别急着重建索引。
  4. 维度能截就截(Matryoshka),内存和延迟双赢。
  5. 结构化过滤 + 向量检索用子查询或调大 ef_search 解决"后过滤漏数据"。
  6. 真到亿级再考虑拆专用向量库,别提前过度设计。

本文所有 SQL / Python 示例均可在 PostgreSQL 18 + pgvector 0.7+ 环境直接运行。距离算子、参数名、GUC 以官方文档为准。

复制全文 生成海报 pgvector PostgreSQL 向量检索 HNSW RAG AI数据库

推荐文章

paint-board:趣味性艺术画板
2024-11-19 07:43:41 +0800 CST
随机分数html
2025-01-25 10:56:34 +0800 CST
Chrome DevTools MCP 深度实战
2026-06-22 20:27:14 +0800 CST
程序员茄子在线接单