pgvector 介绍:在 PostgreSQL 中实现向量相似性搜索
随着人工智能(AI)和机器学习(ML)技术的飞速发展,非结构化数据(如文本、图像、音频、视频)的处理和理解变得越来越重要。这些技术的核心环节之一是将非结构化数据转换为高维向量(Vector Embeddings),这些向量能够捕捉数据的语义信息。随之而来的挑战是如何高效地存储、索引和检索这些向量,尤其是根据向量之间的相似性进行搜索,即向量相似性搜索(Vector Similarity Search)。
传统的数据库系统主要设计用于处理结构化数据和精确匹配查询,对于基于语义相似性的高维向量检索显得力不从心。虽然市面上涌现出许多专门的向量数据库(如 Milvus, Pinecone, Weaviate 等),但对于许多已经深度使用 PostgreSQL 的团队来说,引入一个新的数据库系统会带来额外的架构复杂性、运维成本和数据同步问题。
正是在这样的背景下,pgvector
应运而生。pgvector
是一个开源的 PostgreSQL 扩展,它使得用户可以直接在成熟、稳定、功能丰富的 PostgreSQL 数据库中存储向量数据,并执行高效的向量相似性搜索。它的出现极大地降低了在现有技术栈中引入向量搜索能力的门槛,让开发者能够利用熟悉的 SQL 接口和 PostgreSQL 生态系统来构建强大的 AI 应用。
本文将详细介绍 pgvector
,涵盖其核心概念、安装配置、基本使用、索引机制、性能优化以及实际应用场景,旨在帮助读者全面理解如何在 PostgreSQL 中利用 pgvector
实现向量相似性搜索。
一、 核心概念:向量嵌入与相似性搜索
在深入 pgvector
之前,我们先回顾一下两个核心概念:
-
向量嵌入(Vector Embeddings):
向量嵌入是一种将复杂数据(如单词、句子、图像、用户行为等)映射到低维或高维实数向量空间的技术。这种映射的目标是让语义上相似的数据点在向量空间中的距离也相对接近。生成这些向量的模型通常是预训练的深度学习模型,例如:- 文本嵌入: Word2Vec, GloVe, fastText, BERT, Sentence-BERT, OpenAI Ada, M3E 等。
- 图像嵌入: ResNet, VGG, CLIP, ViT 等。
- 多模态嵌入: CLIP(连接文本和图像)等。
- 这些模型将输入数据转换为固定长度的向量,向量的每个维度代表数据某个抽象的特征。例如,一个通过 Sentence-BERT 生成的句子向量可能包含 768 个维度。
-
向量相似性搜索(Vector Similarity Search):
给定一个查询向量(Query Vector),向量相似性搜索的目标是在一个大规模向量集合中,找出与该查询向量最相似(即距离最近)的 K 个向量,这个过程也称为 K-最近邻(K-Nearest Neighbors, KNN)搜索。衡量向量之间相似性的常用距离度量包括:- 欧氏距离(Euclidean Distance, L2 Distance): 衡量向量空间中两点之间的直线距离。距离越小,越相似。计算公式为:
sqrt(sum((x_i - y_i)^2))
。 - 余弦相似度(Cosine Similarity)/ 余弦距离(Cosine Distance): 衡量两个向量方向上的一致性,与向量的长度无关。相似度范围是 [-1, 1],值越大越相似。余弦距离通常定义为
1 - Cosine Similarity
,范围是 [0, 2],距离越小,越相似。计算公式(相似度):dot(x, y) / (norm(x) * norm(y))
。 - 内积(Inner Product):
dot(x, y) = sum(x_i * y_i)
。对于 L2 归一化(长度为 1)的向量,内积等价于余弦相似度。有时也用负内积作为距离(距离越小越相似)。
- 欧氏距离(Euclidean Distance, L2 Distance): 衡量向量空间中两点之间的直线距离。距离越小,越相似。计算公式为:
pgvector
支持上述主要的距离度量,让用户可以根据具体应用场景和嵌入模型的特性选择最合适的度量方式。
二、 pgvector 的核心特性
pgvector
作为 PostgreSQL 的扩展,提供了以下关键功能:
vector
数据类型: 引入了一个新的数据类型vector(n)
,用于存储n
维的浮点数向量。创建表时可以像使用integer
或text
一样使用它。- 距离计算操作符: 提供了用于计算向量之间距离的 SQL 操作符:
<->
:欧氏距离(L2 Distance)。<#>
:负内积(Negative Inner Product)。对于归一化向量,这可以用来排序以获得最大内积(最相似)。<=>
:余弦距离(Cosine Distance)。
- 索引支持(ANN): 为了加速大规模向量集合的相似性搜索,
pgvector
支持构建近似最近邻(Approximate Nearest Neighbor, ANN)索引。ANN 索引通过牺牲一定的精度(可能找不到绝对最近的邻居,但能找到非常接近的)来换取查询速度的大幅提升。pgvector
目前主要支持两种类型的 ANN 索引:- IVFFlat (Inverted File with Flat Compression): 基于 K-Means 聚类。它将向量空间划分为多个区域(Voronoi 单元),查询时只搜索查询向量所在区域及其附近区域内的向量。
- HNSW (Hierarchical Navigable Small World): 基于图的索引结构。它构建了一个多层的导航图,查询时从顶层粗粒度的图开始,逐步导航到底层精细的图,快速定位到查询向量的近邻。HNSW 通常能提供更好的召回率和查询性能,但构建时间和内存消耗相对较高。
- 与 SQL 的无缝集成: 最大的优势在于,向量搜索可以完全融入到标准的 SQL 查询中。你可以轻松地将向量相似性搜索与传统的
WHERE
子句过滤、JOIN
操作、聚合函数等结合起来,实现复杂的混合查询。 - 利用 PostgreSQL 生态: 作为扩展,
pgvector
可以充分利用 PostgreSQL 成熟的特性,如事务(ACID)、备份恢复、权限管理、连接池、各种客户端库和工具等。
三、 安装与配置
安装 pgvector
通常很简单:
- 前提: 需要安装 PostgreSQL(
pgvector
对版本有一定要求,请查阅官方文档获取最新兼容信息,通常需要较新版本,如 PostgreSQL 11+)。 - 安装扩展:
- 使用包管理器(推荐): 在基于 Debian/Ubuntu 的系统上,通常可以使用
apt-get install postgresql-<version>-pgvector
;在基于 RHEL/CentOS 的系统上,可以使用yum
或dnf
安装相应的包(可能需要配置 PGDG 仓库)。 - 从源码编译: 如果包管理器中没有或者需要特定版本,可以从
pgvector
的 GitHub 仓库下载源码,按照说明进行编译和安装。这通常涉及make
和make install
命令。
- 使用包管理器(推荐): 在基于 Debian/Ubuntu 的系统上,通常可以使用
- 在数据库中启用扩展: 连接到你想要使用
pgvector
的数据库,然后执行 SQL 命令:
sql
CREATE EXTENSION vector;
你可以通过\dx
命令(在psql
客户端中)来确认扩展是否已成功安装并启用。
四、 基本使用
1. 创建包含向量列的表
假设我们要存储文档的嵌入向量,每个向量有 768 维:
sql
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
embedding VECTOR(768) -- 定义一个 768 维的向量列
);
2. 插入向量数据
向量数据在 SQL 中通常表示为浮点数的数组字符串形式:
“`sql
— 假设 embedding_vector_1 和 embedding_vector_2 是 Python 等环境生成的列表或 NumPy 数组
— 在插入时,将其转换为 ‘[f1,f2,…,f768]’ 格式的字符串
INSERT INTO documents (content, embedding) VALUES
(‘这是一篇关于 pgvector 的文章’, ‘[0.1, 0.2, …, 0.9]’), — 实际应为 768 个浮点数
(‘PostgreSQL 是一个强大的关系数据库’, ‘[0.3, 0.1, …, 0.8]’);
“`
3. 执行精确 K-NN 搜索
不使用索引时,pgvector
会执行精确(暴力)的 K-NN 搜索,即计算查询向量与表中所有向量的距离,然后排序。
假设我们有一个查询文本 “介绍向量数据库”,并已将其转换为查询向量 query_vector
(一个包含 768 个浮点数的列表/数组,转换为字符串形式 [q1, q2, ..., q768]
)。
使用欧氏距离 (L2) 查找最相似的 5 个文档:
sql
SELECT id, content, embedding <-> '[q1, q2, ..., q768]' AS distance
FROM documents
ORDER BY embedding <-> '[q1, q2, ..., q768]'
LIMIT 5;
使用余弦距离查找最相似的 5 个文档:
sql
SELECT id, content, embedding <=> '[q1, q2, ..., q768]' AS distance
FROM documents
ORDER BY embedding <=> '[q1, q2, ..., q768]'
LIMIT 5;
使用内积查找最相似(内积最大)的 5 个文档(适用于归一化向量):
注意 <#>
计算的是负内积,所以 ORDER BY
时是升序(最小的负内积即最大的内积)。
sql
SELECT id, content, embedding <#> '[q1, q2, ..., q768]' AS neg_inner_product
FROM documents
ORDER BY embedding <#> '[q1, q2, ..., q768]'
LIMIT 5;
注意: 精确 K-NN 搜索在小数据集上是可行的,但随着数据量的增大,其计算成本呈线性增长(O(N)),性能会急剧下降。对于百万甚至千万级别的向量数据,必须使用索引。
五、 使用 ANN 索引加速查询
为了处理大规模向量数据,需要创建 ANN 索引。
1. IVFFlat 索引
IVFFlat 索引适用于中等规模的数据集,构建速度相对较快。
创建 IVFFlat 索引:
创建索引前,表中需要有一定量的数据(至少 lists
个),以便 K-Means 能够有效聚类。
“`sql
— ‘documents_embedding_ivfflat_idx’ 是索引名
— ‘documents’ 是表名,’embedding’ 是向量列名
— ‘vector_l2_ops’ 表示使用 L2 距离(欧氏距离)
— ‘lists = 100’ 是一个关键参数,表示将数据聚类成 100 个簇(列表)
— lists 的值需要根据数据量 N 调整,常见建议是:
— N < 1M: lists = N / 1000
— N >= 1M: lists = sqrt(N)
CREATE INDEX documents_embedding_ivfflat_idx
ON documents
USING ivfflat (embedding vector_l2_ops) — 使用 L2 距离
WITH (lists = 100);
— 如果使用余弦距离:
— CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
— 如果使用内积:
— CREATE INDEX ON documents USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);
“`
使用 IVFFlat 索引查询:
查询语句与精确搜索相同。PostgreSQL 的查询优化器会自动选择使用合适的索引。
调整查询时性能与精度的平衡 (probes
):
IVFFlat 查询时默认只检查查询向量所属的那个簇。可以通过设置 ivfflat.probes
参数来让它检查更多的邻近簇,以提高召回率(找到真正最近邻的可能性),但会增加查询时间。
“`sql
— 在查询开始前设置 probes 值(会话级别)
SET ivfflat.probes = 10; — 让查询检查 10 个最近的簇
SELECT id, content, embedding <-> ‘[q1, q2, …, q768]’ AS distance
FROM documents
ORDER BY embedding <-> ‘[q1, q2, …, q768]’
LIMIT 5;
— 查询结束后可以重置回默认值
RESET ivfflat.probes;
``
probes的值通常远小于
lists` 的值。需要根据实际应用对召回率和延迟的要求进行调整和测试。
2. HNSW 索引
HNSW 索引通常能提供更高的召回率和查询速度,尤其是在高维和大规模数据集上,但构建索引所需的时间和内存也更多。
创建 HNSW 索引:
“`sql
— ‘documents_embedding_hnsw_idx’ 是索引名
— ‘m’ 是每个节点的最大连接数(影响图的密度和内存占用),推荐 4-64,默认 16
— ‘ef_construction’ 是构建索引时搜索的邻居数量(影响索引质量和构建时间),推荐 64-1024,默认 64
CREATE INDEX documents_embedding_hnsw_idx
ON documents
USING hnsw (embedding vector_l2_ops) — 使用 L2 距离
WITH (m = 16, ef_construction = 64);
— 如果使用余弦距离:
— CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);
— 如果使用内积:
— CREATE INDEX ON documents USING hnsw (embedding vector_ip_ops) WITH (m = 16, ef_construction = 64);
“`
使用 HNSW 索引查询:
查询语句依然不变,优化器会自动使用 HNSW 索引。
调整查询时性能与精度的平衡 (ef_search
):
HNSW 查询性能和召回率可以通过 hnsw.ef_search
参数控制。它决定了在搜索过程中动态维护的候选邻居列表的大小。ef_search
值越大,召回率越高,但查询越慢。ef_search
必须大于等于 LIMIT
的 K 值。
“`sql
— 设置查询时的搜索范围(会话级别)
SET hnsw.ef_search = 100; — 搜索时维护一个大小为 100 的候选列表
SELECT id, content, embedding <-> ‘[q1, q2, …, q768]’ AS distance
FROM documents
ORDER BY embedding <-> ‘[q1, q2, …, q768]’
LIMIT 5;
— 查询结束后可以重置
RESET hnsw.ef_search;
``
ef_search` 的取值需要通过实验来确定最佳平衡点。
3. 选择哪种索引?
- IVFFlat: 构建速度快,内存占用相对较低。适用于数据量不是特别巨大(如百万级以下)或对召回率要求不是极致的场景。更新(INSERT/UPDATE/DELETE)相对友好。
- HNSW: 通常具有更高的查询性能和召回率,尤其是在高维和大规模数据集上。但构建索引耗时较长,内存占用较高。对数据更新(尤其是 UPDATE/DELETE)的性能影响比 IVFFlat 更大。
在实践中,建议根据具体的数据规模、维度、更新频率、以及对查询延迟和召回率的要求进行测试和选择。
六、 结合传统 SQL 进行混合查询
pgvector
的强大之处在于能够将向量搜索与 PostgreSQL 的全部 SQL 功能结合。
示例:查找与查询向量相似,并且发布日期在最近一年的文档
sql
-- 假设 documents 表有一个 published_date 列
SELECT id, content, published_date, embedding <=> '[q1, ..., q768]' AS distance
FROM documents
WHERE published_date >= NOW() - INTERVAL '1 year' -- 先进行传统过滤
ORDER BY embedding <=> '[q1, ..., q768]' -- 再进行向量相似度排序
LIMIT 10;
PostgreSQL 的查询优化器会尝试找到最高效的执行计划。对于这类查询,它可能会先利用 published_date
上的(如果存在)B-Tree 索引进行过滤,缩小范围,然后再对过滤后的结果进行向量相似性计算和排序(可能会利用向量索引,也可能因为过滤后结果集太小而直接进行精确计算)。这种能力使得构建复杂的、结合了元数据过滤和语义相似性搜索的应用变得非常方便。
七、 应用场景
pgvector
使得在 PostgreSQL 中实现以下 AI 相关应用成为可能:
- 语义文本搜索: 超越关键词匹配,理解查询意图,找到语义相关的文档、文章、问答对等。是 RAG(Retrieval-Augmented Generation)系统中 LLaMA 等大语言模型的重要组成部分。
- 推荐系统: 基于用户行为或物品内容的嵌入向量,找到相似的用户或物品,实现个性化推荐。
- 图像/视频检索: 通过图像内容的视觉特征向量,实现以图搜图、以图搜视频。
- 音频分析: 基于音频片段的嵌入向量,进行声音事件识别、音乐推荐等。
- 异常检测: 在向量空间中,远离正常数据簇的点可能代表异常。
- 数据去重/聚类: 找到内容高度相似的重复数据项,或将相似数据自动分组。
八、 优势与局限性
优势:
- 简化技术栈: 无需引入和维护独立的向量数据库系统。
- 数据一致性: 向量数据与元数据存储在同一数据库中,易于保证事务一致性。
- 利用成熟生态: 可以复用 PostgreSQL 的备份、恢复、监控、安全、权限管理、高可用方案等。
- 强大的 SQL 集成: 可以方便地进行混合查询,结合结构化过滤和向量搜索。
- 成本效益: 对于中小型应用,可能比托管的专业向量数据库更具成本效益。
- 开源与社区: 开源项目,有活跃的社区支持和持续的开发。
局限性:
- 性能: 虽然 ANN 索引显著提升了性能,但在极大规模(数十亿级别向量)或极低延迟要求(亚毫秒级)的场景下,可能仍不及专门优化过的、分布式设计的向量数据库。
- 资源消耗: HNSW 索引构建和存储需要较多内存和 CPU 资源。向量搜索本身也是计算密集型操作,可能与数据库的其他工作负载竞争资源。
- 索引更新: 对于 HNSW 索引,频繁的更新和删除操作可能会影响性能和索引质量,可能需要定期重建索引(REINDEX)。
- 水平扩展: PostgreSQL 本身的水平扩展相对复杂(如分片),虽然有 Citus 等解决方案,但与一些原生支持分布式的向量数据库相比,横向扩展向量存储和计算能力可能需要更多工作。
九、 总结
pgvector
是一个非常有价值的 PostgreSQL 扩展,它成功地将向量相似性搜索的能力引入到了这个广受欢迎的关系数据库中。通过提供 vector
数据类型、距离计算操作符以及 IVFFlat 和 HNSW 两种强大的 ANN 索引机制,pgvector
使得开发者能够在熟悉的 PostgreSQL 环境中,使用标准的 SQL 语句,高效地存储、管理和检索高维向量数据。
它极大地降低了构建语义搜索、推荐系统、RAG 等 AI 应用的门槛,特别适合那些希望在现有 PostgreSQL 基础架构上扩展 AI 能力的团队。虽然在超大规模或极端性能要求的场景下可能面临挑战,但对于绝大多数中小型甚至部分大型应用而言,pgvector
提供了一个整合度高、运维简单、功能强大的解决方案。随着 AI 技术的普及,pgvector
无疑将成为 PostgreSQL 生态中越来越重要的一员。