字节笔记本
2026年8月28日
Supabase pgvector:Postgres 里的 AI 工具箱
做 RAG、语义搜索、知识库问答时,很多人第一反应是上独立向量库。其实如果你已经在用 Postgres,尤其是 Supabase,开一个 pgvector 扩展往往就够了:向量、元数据、权限、备份都在同一套数据库里。
Supabase 把这套能力整理成了开源 AI 工具箱:用 Postgres + pgvector 存嵌入,用客户端库读写,再用 Edge Functions 或外部模型生成向量。下面按「能跑起来」的顺序走一遍。
为什么用 Postgres 存向量
独立向量库(Pinecone、Weaviate 等)在超大规模 ANN 检索上很强,但多一套基础设施也意味着多一套同步、权限和运维。很多业务数据本来就在 Postgres:用户、文档、权限、审计日志。向量放旁边,用 SQL 做过滤就很自然:
- 「只搜某个 workspace 的文档」
- 「只返回公开且未删除的条目」
- 「按创建时间 + 相似度一起排序」
Supabase 的立场很直白:最好的向量库,往往是你已经在用的那套库。
官方总览见 AI & Vectors。
启用 pgvector
扩展在 Postgres 里的名字叫 vector(不是 pgvector)。在 SQL Editor 里执行:
create extension vector
with
schema extensions;也可以在 Dashboard → Database → Extensions 里搜 vector 打开。
建一张带向量列的表
维度必须和嵌入模型一致。例如开源模型 gte-small 是 384 维:
create table documents (
id serial primary key,
title text not null,
body text not null,
embedding extensions.vector(384)
);换 OpenAI text-embedding-3-small 时,常见是 1536 维,把括号里的数字改掉即可。混用不同模型产出的向量去比距离,结果没有意义。
写入嵌入
思路很简单:先把文本变成向量,再和正文一起 insert。前端或 Node 里可以用 Transformers.js 本地算(不必每次打云端 API):
import { pipeline } from '@huggingface/transformers'
const generateEmbedding = await pipeline(
'feature-extraction',
'Supabase/gte-small'
)
const body = 'Hello world!'
const output = await generateEmbedding(body, {
pooling: 'mean',
normalize: true,
})
const embedding = Array.from(output.data)
const { error } = await supabase.from('documents').insert({
title: 'First post!',
body,
embedding,
})生产环境更常见的是:文档入库时用 Edge Function / 后台任务调 OpenAI、Hugging Face 等生成嵌入,再写回同一行。
相似度查询
pgvector 提供三种距离算子:
| 算子 | 含义 |
|---|---|
<-> | 欧氏距离 |
<#> | 负内积 |
<=> | 余弦距离 |
向量已归一化时,点积通常最快。PostgREST 目前不直接暴露这些算子,所以官方做法是包一层 SQL 函数,再用客户端 rpc() 调用:
create or replace function match_documents (
query_embedding extensions.vector(384),
match_threshold float,
match_count int
)
returns table (
id bigint,
title text,
body text,
similarity float
)
language sql stable
as $$
select
documents.id,
documents.title,
documents.body,
1 - (documents.embedding <=> query_embedding) as similarity
from documents
where 1 - (documents.embedding <=> query_embedding) > match_threshold
order by (documents.embedding <=> query_embedding) asc
limit match_count;
$$;客户端侧:
const { data: documents } = await supabase.rpc('match_documents', {
query_embedding: embedding,
match_threshold: 0.78,
match_count: 10,
})阈值先变成向量,再丢进 match_documents。match_threshold 要按自己的数据试,太低会捞一堆不相关的,太高又容易空结果。
更细的列定义和过滤示例见 Vector columns 和 pgvector 扩展说明。
和关键词检索怎么配合
纯语义搜索擅长「说法不同、意思接近」;纯关键词擅长专有名词、错误码、接口名。文档搜索里两者经常要一起用,也就是混合检索(hybrid search):向量召回一批,再和全文检索结果融合排序。
Supabase 文档把语义搜索、关键词搜索、混合搜索都列成可落地的方向,并有 Next.js + OpenAI 文档问答、CLIP 图像搜索等模板可参考。
数据量大了再加索引
表小的时候暴力扫描够用。行数上去之后再给向量列加 IVFFlat 或 HNSW 索引。注意:若查询里还带 category_id = … 这类过滤,索引可能返回不足 LIMIT 条;需要按官方建议做迭代扫描,或把过滤条件设计进函数里。
什么时候该换独立向量库
- 向量量级到亿级、延迟要求极严
- 需要复杂的多租户向量产品能力,而你又不想在 Postgres 上自己堆
- 团队已经把向量链路和业务库强行拆开运维
否则,先用 Postgres + pgvector 把产品跑通通常更省事。不少团队(包括从 Pinecone / 自建 RDS 迁过来的案例)最后发现:业务过滤、权限和备份跟向量绑在一起,反而更简单。
小结
- 打开
vector扩展 - 建表时向量维度对齐嵌入模型
- 写入时「文本 → embedding → insert」
- 用 SQL 函数 +
rpc()做相似度检索 - 需要时再上混合检索和向量索引
一套 Postgres,就能同时扛关系数据和向量检索。对多数中小规模的 RAG / 语义搜索来说,这已经是够用且好维护的起点。