pgvector是PostgreSQL的扩展,为关系型数据库增加了向量数据类型和相似度检索能力。借助pg库,Node.js应用可以复用现有的PostgreSQL连接和事务机制,不必引入独立的向量数据库。本文从扩展启用、表结构、数据写入、相似度查询到索引优化,给出可直接落地的实现方案。

一、启用pgvector扩展并配置Node.js连接
首先需要在PostgreSQL实例中安装pgvector扩展。如果使用的是本地开发库,可以通过源码编译或者直接使用已经打包好的版本。安装完成后,在目标数据库中执行创建扩展语句:
CREATE EXTENSION IF NOT EXISTS vector;
执行成功后,数据库就具备了vector数据类型以及对应的距离运算符。可以使用以下SQL确认扩展是否就绪:
SELECT extname, extversion FROM pg_extension WHERE extname = 'vector';
Node.js端推荐使用pg库,它是最常用的PostgreSQL客户端,支持连接池、参数化查询和事务。安装命令如下:
npm install pg
接着创建一个数据库连接池。连接池能够复用连接,避免每次查询都重新建立TCP连接,适合向量查询这种可能频繁调用的场景。
const { Pool } = require('pg');
const pool = new Pool({
user: 'postgres',
host: '127.0.0.1',
database: 'vectordb',
password: 'your_password',
port: 5432,
max: 20,
idleTimeoutMillis: 30000,
});
module.exports = pool;
上面的配置使用了127.0.0.1作为本地地址,用户名和密码需要替换成实际环境的值。连接池的最大连接数设置为20,空闲超时30秒,适合中等规模的应用。
二、创建向量表并写入数据
pgvector允许在表中使用vector(n)类型,其中n表示向量维度。维度一旦确定,插入的向量必须保持一致,否则会报错。常见的嵌入模型输出维度有384、768、1536等。例如用text-embedding-3-small模型产生的向量是1536维,可以这样建表:
CREATE TABLE documents ( id serial PRIMARY KEY, content text NOT NULL, embedding vector(1536) NOT NULL );
写入向量时,pgvector接受用方括号包裹的浮点数组字符串,比如:[0.12,0.34,0.56]。Node.js中需要把数组转换成这种格式。下面是一个插入单条记录的函数:
async function insertDocument(content, embeddingArray) {
const embeddingString = '[' + embeddingArray.join(',') + ']';
const query = 'INSERT INTO documents (content, embedding) VALUES ($1, $2) RETURNING id';
const values = [content, embeddingString];
const result = await pool.query(query, values);
return result.rows[0].id;
}
如果向量维度较高,手动拼接字符串容易出错。可以封装一个工具函数,把数组统一转为pgvector需要的字符串,同时检查维度是否符合表定义。批量插入时建议使用事务或者pg库的批量操作,减少网络往返。以下是一个批量插入示例:
async function insertBatch(documents) {
const client = await pool.connect();
try {
await client.query('BEGIN');
for (const doc of documents) {
const embeddingString = '[' + doc.embedding.join(',') + ']';
await client.query(
'INSERT INTO documents (content, embedding) VALUES ($1, $2)',
[doc.content, embeddingString]
);
}
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
}
在生产环境中,上千条向量的写入建议使用PostgreSQL的COPY命令或者批量INSERT语法,避免逐条提交带来的性能损耗。pg库本身支持使用流式查询和COPY FROM,但需要先把数据格式化成适合的文本。
三、执行相似度查询与距离度量选择
pgvector提供了三种距离度量方式:余弦距离、欧氏距离和负内积。余弦距离衡量方向差异,适合文本向量;欧氏距离衡量绝对距离,适合低维坐标;负内积常用于最大化相似度的场景,比如某些推荐模型。对应的运算符分别是<=>、<->和<#>。
查询与某个向量最相似的记录,可以使用如下SQL:
SELECT id, content, embedding <=> $1 AS distance FROM documents ORDER BY embedding <=> $1 LIMIT 10;
这里的$1是查询向量的字符串,例如[0.1,0.2,...,0.9]。Node.js中封装的查询函数可以接收一个数组,把它转换成向量字符串,然后执行查询:
async function searchSimilar(queryEmbedding, limit = 10) {
const embeddingString = '[' + queryEmbedding.join(',') + ']';
const query = `
SELECT id, content, embedding <=> $1 AS distance
FROM documents
ORDER BY embedding <=> $1
LIMIT $2
`;
const result = await pool.query(query, [embeddingString, limit]);
return result.rows;
}
注意距离值越小表示越相似。余弦距离的范围是0到2,0表示方向完全一致。如果需要转换成相似度分数,可以用1减去距离。另外,如果要按照负内积排序,运算符写作<#>,此时返回的值越大表示越相似。
不同距离函数对向量的模长敏感程度不同。如果向量已经做了归一化处理,余弦距离和欧氏距离的排序结果基本一致;如果没有归一化,欧氏距离会偏向模长大的向量。实际项目中应根据嵌入模型输出是否归一化来选择合适的度量。
四、创建向量索引提升查询性能
当数据量达到几万条后,全表扫描的相似度查询会变得非常慢。pgvector内置了IVFFlat和HNSW两种近似最近邻索引。IVFFlat基于倒排文件,先对向量空间进行聚类,查询时只搜索最接近的一部分聚类;HNSW基于分层可导航小世界图,召回率通常更高,但构建时间更长、内存占用更大。
给documents表创建HNSW索引可以使用以下SQL:
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
这里vector_cosine_ops表示使用余弦距离作为索引的度量。如果要使用欧氏距离,应换成vector_l2_ops;使用内积则用vector_ip_ops。索引度量需要与查询时使用的运算符保持一致,否则索引不会被利用。
IVFFlat索引的创建相对简单,但需要在创建前先有足够的数据量,并且要指定聚类的数量。示例:
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
创建索引后,可以使用EXPLAIN ANALYZE查看查询是否走了索引。如果执行计划中出现Index Scan using documents_embedding_idx,说明索引生效。另外,HNSW索引会占用较多内存,建议根据服务器内存调整maintenance_work_mem参数,并定期使用VACUUM清理死元组。
在实际应用中,向量查询常常与结构化过滤结合,例如只搜索某一分类下的文档。pgvector索引可以和其他B-tree索引配合使用,PostgreSQL会自动选择合适的执行计划。不过要注意,如果过滤条件选出的行数很少,全表扫描可能比索引更快,需要根据实际数据分布测试。
五、生产环境中的注意事项
将pgvector用于生产之前,需要考虑几个关键点。首先是向量维度与存储空间:1536维的向量大约占用6KB存储,百万级数据会达到数GB,需要规划好磁盘和内存。其次是查询的响应时间,近似索引虽然快,但召回率不是100%,可以通过增大HNSW的ef_search参数来平衡速度与精度。
另一个常见问题是连接池的配置。向量查询可能耗时较长,如果连接池中的连接都被慢查询占用,后续请求会排队。可以为慢查询设置单独的连接池,或者使用pg库的query_timeout选项,避免单个请求阻塞整个服务。
此外,pgvector扩展升级时需要留意版本兼容性。PostgreSQL的小版本升级通常不影响扩展,但大版本升级可能需要重新编译扩展。建议在测试环境先验证升级流程,并备份向量数据。
最后,如果团队已经深度使用PostgreSQL,pgvector是一个自然的过渡方案,能减少技术栈的复杂度。但如果是超大规模的向量检索,比如十亿级数据,还是需要评估专用向量数据库的性能优势。