UXVector 向量库

1. 概述

UXVector是UXDB为AI场景量身打造的向量检索扩展。它将向量相似度计算能力内化到数据库内核,企业无需引入独立的专用向量数据库,即可在现有UXDB基础设施上直接构建生成式AI、语义搜索等智能化应用。其核心能力包括:

  1. 精确与近似最近邻搜索——默认精确搜索保证完美召回率,可选 HNSW / IVFFlat 索引以召回率换查询速度;
  2. 多种向量类型——单精度(vector)、半精度(halfvec)、二进制(bit)与稀疏向量(sparsevec);
  3. 多种距离度量——L2 距离、内积、余弦距离、L1 距离、汉明距离和 Jaccard 距离;
  4. 完整继承UXDB能力——ACID 事务、时间点恢复(PITR)、流复制、JOIN、分区、全文检索混合搜索;
  5. 兼容任何支持 UXDB客户端的编程语言

2. 快速入门

以下示例为最简可运行流程,生产用法见后续章节。

2.1 启用扩展

在每个需要使用 UXVector 的数据库中执行一次(需要相应权限):

CREATE EXTENSION vector;

可选:验证扩展版本:

SELECT extversion FROM ux_extension WHERE extname = 'vector';

2.2 创建向量表

创建一个具有 3 维向量列的表:

CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));

2.3 插入向量

INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');

2.4 查询最近邻

通过 L2 距离获取最近的 5 个邻居:

SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;

还支持内积(<#>)、余弦距离(<=>)和 L1 距离(<+>)等运算符,详见第 5 章

注意<#> 返回内积,因为 UXVector 仅支持对操作符进行升序索引扫描。计算实际内积需将结果乘以 -1。

3. 向量数据类型

类型说明存储大小可索引维度上限存储维度上限
vector单精度浮点向量4 × 维度 + 8 字节2,000 维16,000 维
halfvec半精度浮点向量2 × 维度 + 8 字节4,000 维16,000 维
bit二进制向量维度 / 8 + 8 字节64,000 维
sparsevec稀疏向量8 × 非零元素数 + 16 字节1,000 个非零元素16,000 个非零元素

类型选择建议:

  • vector:默认选择。维度需与所用的 Embedding 模型输出一致,常见如 OpenAI text-embedding-3-small(1536 维)、BGE-M3(1024 维)、ChatGLM Embedding(1024 维);
  • halfvec:大规模存储场景,空间减半、精度损失极小;超过 vector 索引维度上限(2,000 维)时的首选降精度方案;
  • bit:配合 binary_quantize() 做二进制量化,适合超大规模近似检索;
  • sparsevec:高维稀疏向量(如 BM25 分数、SPLADE 输出)。

约束与限制:

  • 向量所有元素必须是有限值,不允许 NaNInfinity-Infinity
  • NULL 向量不会被索引收录;使用余弦距离时,零向量也不会被索引收录(无法计算方向);
  • 同一列向量维度必须相同。如需存储不同维度,可使用不带维度的 vector 类型,但只能对同维度子集建索引(需借助表达式索引 + 部分索引)。

各类型建表示例:

-- 单精度
CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));

-- 半精度
CREATE TABLE items_half (id bigserial PRIMARY KEY, embedding halfvec(3));

-- 二进制
CREATE TABLE items_bit (id bigserial PRIMARY KEY, embedding bit(3));

-- 稀疏向量
CREATE TABLE items_sparse (id bigserial PRIMARY KEY, embedding sparsevec(5));

4. 数据存储与管理

4.1 增删改查

-- 为已有表添加向量列
ALTER TABLE items ADD COLUMN embedding vector(3);

-- 插入向量
INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');

-- 稀疏向量格式:{index:value}/dimensions,索引从 1 开始
INSERT INTO items_sparse (embedding)
VALUES ('{1:1,3:2,5:3}/5'), ('{1:4,3:5,5:6}/5');

-- 更新向量
UPDATE items SET embedding = '[1,2,3]' WHERE id = 1;

-- 删除向量
DELETE FROM items WHERE id = 1;

-- Upsert
INSERT INTO items (id, embedding) VALUES (1, '[1,2,3]')
    ON CONFLICT (id) DO UPDATE SET embedding = EXCLUDED.embedding;

4.2 批量加载

大批量导入建议使用 COPY,并遵循"先加载数据、后创建索引"的原则(见第 9 章):

COPY items (embedding) FROM STDIN WITH (FORMAT BINARY);

5. 查询与距离运算符

5.1 距离运算符总览

运算符含义适用类型典型场景
<->L2 距离(欧几里得距离)所有向量类型通用相似度搜索
<#>内积所有向量类型已归一化向量的最大内积搜索(如 OpenAI embeddings)
<=>余弦距离所有向量类型文本 / 图像嵌入相似度(推荐)
<+>L1 距离(曼哈顿距离)所有向量类型特殊场景
<~>汉明距离二进制向量二进制量化检索
<%>Jaccard 距离二进制向量二进制量化检索

注意

  • <#> 返回负内积(UXDB索引扫描仅支持升序)。实际内积 = <#> 结果 × -1;
  • 余弦相似度 = 1 - 余弦距离<=>);
  • 距离越越相似。

5.2 常用查询模式

-- 1. 获取向量的最近邻(L2 距离)
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;

-- 2. 获取某一行的最近邻(自连接排除自身)
SELECT * FROM items WHERE id != 1
    ORDER BY embedding <-> (SELECT embedding FROM items WHERE id = 1)
    LIMIT 5;

-- 3. 获取一定距离内的行(范围查询)
SELECT * FROM items WHERE embedding <-> '[3,1,2]' < 5;

-- 4. 获取距离值本身
SELECT embedding <-> '[3,1,2]' AS distance FROM items;

-- 5. 实际内积(乘 -1)
SELECT (embedding <#> '[3,1,2]') * -1 AS inner_product FROM items;

-- 6. 余弦相似度
SELECT 1 - (embedding <=> '[3,1,2]') AS cosine_similarity FROM items;

-- 7. 按分组取平均向量
SELECT category_id, AVG(embedding) FROM items GROUP BY category_id;

索引使用要点:查询必须包含 ORDER BY(且排序表达式必须直接是距离算子结果并按升序)加 LIMIT 才能使用近似索引。ORDER BY 1 - (embedding <=> ...) 这类表达式不会使用索引。

5.3 常用函数与运算符参考

向量函数

函数说明
binary_quantize(vector) → bit二进制量化
cosine_distance(vector, vector) → double precision余弦距离
inner_product(vector, vector) → double precision内积
l1_distance(vector, vector) → double precisionL1 距离
l2_distance(vector, vector) → double precisionL2 距离
l2_normalize(vector) → vectorL2 归一化
subvector(vector, integer, integer) → vector提取子向量
vector_dims(vector) → integer维度数
vector_norm(vector) → double precision欧几里得范数

向量运算符+(逐元素加)、-(逐元素减)、*(逐元素乘)、||(拼接)。

聚合函数avg(vector) → vectorsum(vector) → vector(同样适用于 halfvec)。

6. 索引

默认情况下,UXVector 执行精确最近邻搜索,提供完美的召回率。用户可添加索引以启用近似最近邻搜索(ANN),用少量召回率换取更快的查询速度。与典型索引不同,添加近似索引后,查询结果可能与精确搜索不同。

支持的索引类型:

  1. HNSW——多层可导航小世界图;
  2. IVFFlat——倒排文件(聚类分桶)索引。

为您要使用的每个距离运算符分别创建一个索引(一个索引只服务一种距离度量)。

6.1 HNSW 索引

HNSW 构建一个多层图。其查询性能优于 IVFFlat(速度-召回率权衡更优),但构建时间更慢、占用内存更多。由于不需要训练步骤,可以在空表上创建

-- L2 距离
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);

-- 内积
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);

-- 余弦距离
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);

-- L1 距离
CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);

-- 汉明距离(二进制向量)
CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);

-- Jaccard 距离(二进制向量)
CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);

注意:对于 halfvec 使用 halfvec_l2_ops,对于 sparsevec 使用 sparsevec_l2_ops(其他距离运算符类似),完整对照见第 6.3 节

索引构建参数

CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)
WITH (m = 16, ef_construction = 64);
参数默认值说明
m16每层最大连接数,越大召回率越高但内存占用越多
ef_construction64构建图的动态候选列表大小,越大建图质量越高但构建越慢

查询期参数

参数默认值说明
hnsw.ef_search40搜索动态候选列表大小,越大召回率越高、速度越慢
hnsw.iterative_scanoff迭代索引扫描模式(strict_order / relaxed_order),提升过滤查询召回率
hnsw.max_scan_tuples20,000迭代扫描时最大访问元组数
hnsw.scan_mem_multiplier1迭代扫描内存倍数(基于 work_mem
-- 提高召回率
SET hnsw.ef_search = 100;

-- 仅在当前事务内生效(推荐,避免影响其他会话)
BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT ...;
COMMIT;

6.2 IVFFlat 索引

IVFFlat 将向量划分为多个列表(list),查询时只搜索与查询向量最接近的若干列表。其构建速度更快、内存占用更少,但查询性能低于 HNSW(速度-召回率权衡)。

实现良好召回率的三个关键

  1. 在表已有数据之后再创建索引(IVFFlat 需要对数据做 k-means 聚类训练);
  2. 选择合适的 lists 数量:数据不超过 100 万行时,从 行数 / 1000 开始;超过 100 万行时,从 sqrt(行数) 开始;
  3. 查询时指定适当的探测数(probes):越高召回率越好,越低速度越快——从 sqrt(lists) 开始。
-- L2 距离
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);

-- 内积
CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);

-- 余弦距离
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

-- 汉明距离(二进制向量)
CREATE INDEX ON items USING ivfflat (embedding bit_hamming_ops) WITH (lists = 100);

注意:对于 halfvec 使用 halfvec_l2_ops(其他距离运算符类似)。

查询期参数

参数默认值说明
ivfflat.probes1查询探测的列表数;设为 lists 总数时退化为精确搜索
ivfflat.iterative_scanoff迭代扫描(relaxed_order),提升过滤查询召回率
ivfflat.max_probes迭代扫描时的最大探测数
SET ivfflat.probes = 10;

6.3 操作符类对照表

各向量类型在不同距离度量下可用的索引操作符类:

距离度量vectorhalfvecbitsparsevec
L2vector_l2_opshalfvec_l2_opssparsevec_l2_ops
内积vector_ip_opshalfvec_ip_ops
余弦vector_cosine_opshalfvec_cosine_opssparsevec_cosine_ops
L1vector_l1_ops
汉明bit_hamming_ops
Jaccardbit_jaccard_ops

6.4 索引选择建议

场景推荐索引原因
数据量 < 100 万行HNSW查询快、召回率高
数据量 > 100 万行HNSW(大 ef_search速度-召回率综合最优
频繁插入更新IVFFlat增量维护成本低
对召回率要求极高HNSW(大 ef_search可逼近精确搜索
内存受限 / 构建速度优先IVFFlat构建快、内存占用少
空表即需建索引HNSW无需训练步骤

7. 过滤搜索与混合搜索

7.1 过滤查询

近似索引在带 WHERE 过滤条件时召回率会下降,推荐开启迭代索引扫描

SET hnsw.iterative_scan = strict_order;  -- 保证严格有序
SET hnsw.iterative_scan = relaxed_order; -- 允许宽松有序,过滤场景召回率更好

对少量固定值的过滤可使用部分索引:

CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)
    WHERE (category_id = 123);

7.2 混合搜索(与全文检索结合)

SELECT id, content FROM items, plainto_tsquery('hello search') query
    WHERE textsearch @@ query
    ORDER BY ts_rank_cd(textsearch, query) DESC
    LIMIT 5;

8. 进阶用法

8.1 二进制量化 + 重排序

先用二进制量化索引粗筛,再用原向量精排,兼顾内存与精度:

CREATE INDEX ON items USING hnsw
    ((binary_quantize(embedding)::bit(3)) bit_hamming_ops);

SELECT * FROM (
    SELECT * FROM items
    ORDER BY binary_quantize(embedding)::bit(3) <~> binary_quantize('[1,-2,3]')
    LIMIT 20
) ORDER BY embedding <=> '[1,-2,3]' LIMIT 5;

8.2 半精度索引(超高维场景)

超过 vector 2,000 维索引上限时,用表达式索引将列降为半精度:

CREATE INDEX ON items USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops);

8.3 子向量索引

CREATE INDEX ON items USING hnsw
    ((subvector(embedding, 1, 3)::vector(3)) vector_cosine_ops);

9. 性能调优建议

9.1 索引构建调优(DBA)

-- 建索引前增大维护内存(图能放入内存时构建显著加快)
SET maintenance_work_mem = '8GB';

-- 增加并行工作者(默认 2)
SET max_parallel_maintenance_workers = 7;
-- 并行工作者较多时可能还需调整 max_parallel_workers(默认 8)

在 Docker 中增大 maintenance_work_mem 时,需同步设置 --shm-size,否则并行 HNSW 构建可能报错。

  • 生产环境建索引建议使用 CREATE INDEX CONCURRENTLY,避免阻塞写入;
  • shared_buffers 通常建议设置为服务器内存的 25%。

9.2 数据加载调优

  • 使用 COPY 批量加载;
  • 先加载初始数据,再创建索引(尤其是 IVFFlat,必须先有数据):
DROP INDEX IF EXISTS idx_items_hnsw;
-- ... 批量 COPY 数据 ...
CREATE INDEX idx_items_hnsw ON items
    USING hnsw (embedding vector_cosine_ops);

9.3 查询调优

  • 使用 EXPLAIN (ANALYZE, BUFFERS) 定位性能瓶颈;
  • 精确搜索提速:增大 max_parallel_workers_per_gather;向量已归一化(如 OpenAI embeddings)时改用内积 <#> 性能最佳;
  • 近似搜索提速:二进制量化 + 重排序,并尽量让索引常驻内存;IVFFlat 可适当增大 lists(以召回率为代价);
  • VACUUM 慢时,可先 REINDEX INDEX CONCURRENTLY 再执行 VACUUM

9.4 存储与扩展

  • halfvec 替代 vector 减小工作集;
  • 需要更高精度时可用 double precision[] / numeric[] 存储原始数据,配合表达式索引降精度检索;
  • 垂直扩展:增加单机内存 / CPU / 存储;
  • 水平扩展:使用流复制、MPP等分片方案;
  • 多租户场景使用列表分区实现租户隔离。

9.5 监控与召回率验证

  • 使用 ux_stat_statements 监控 SQL 性能;
  • 通过对比近似搜索与精确搜索的结果监控召回率:
BEGIN;
SET LOCAL enable_indexscan = off;  -- 关闭索引扫描走精确搜索
SELECT ...;
COMMIT;

10. 注意事项与常见问题

问题说明
单表可存多少向量?非分区表默认上限 32 TB;分区表可拥有数千个该大小的分区
是否支持复制?支持,基于 WAL,可用于流复制和时间点恢复
超过 2,000 维如何索引?使用 halfvec(4,000 维)、二进制量化(64,000 维)、子向量索引或先降维
为什么查询没有走索引?查询必须包含 ORDER BY(直接为距离算子结果且升序)+ LIMIT;表达式包裹的距离不会使用索引
索引必须放入内存吗?不是必须,但索引常驻内存时性能更好
小表需要索引吗?数据量小于约 1,000 行时,全表顺序扫描可能更快
建索引后结果变了?正常现象。近似索引以少量召回率换取速度,结果与精确搜索可能不同

其他注意事项:

  • 同一列向量维度必须相同,且需与 Embedding 模型输出维度一致;
  • 向量列建议设置 NOT NULL 约束(NULL 向量不会被索引收录);
  • HNSW 索引支持并发读写;
  • 索引创建期间的阶段可通过 ux_stat_progress_create_index 观察:HNSW 依次为 initializingloading tuples;IVFFlat 依次为 initializingperforming k-meansassigning tuplesloading tuples