UXVector 向量库
1. 概述
UXVector是UXDB为AI场景量身打造的向量检索扩展。它将向量相似度计算能力内化到数据库内核,企业无需引入独立的专用向量数据库,即可在现有UXDB基础设施上直接构建生成式AI、语义搜索等智能化应用。其核心能力包括:
- 精确与近似最近邻搜索——默认精确搜索保证完美召回率,可选 HNSW / IVFFlat 索引以召回率换查询速度;
- 多种向量类型——单精度(
vector)、半精度(halfvec)、二进制(bit)与稀疏向量(sparsevec); - 多种距离度量——L2 距离、内积、余弦距离、L1 距离、汉明距离和 Jaccard 距离;
- 完整继承UXDB能力——ACID 事务、时间点恢复(PITR)、流复制、JOIN、分区、全文检索混合搜索;
- 兼容任何支持 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 输出)。
约束与限制:
- 向量所有元素必须是有限值,不允许
NaN、Infinity、-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 precision | L1 距离 |
l2_distance(vector, vector) → double precision | L2 距离 |
l2_normalize(vector) → vector | L2 归一化 |
subvector(vector, integer, integer) → vector | 提取子向量 |
vector_dims(vector) → integer | 维度数 |
vector_norm(vector) → double precision | 欧几里得范数 |
向量运算符:+(逐元素加)、-(逐元素减)、*(逐元素乘)、||(拼接)。
聚合函数:avg(vector) → vector、sum(vector) → vector(同样适用于 halfvec)。
6. 索引
默认情况下,UXVector 执行精确最近邻搜索,提供完美的召回率。用户可添加索引以启用近似最近邻搜索(ANN),用少量召回率换取更快的查询速度。与典型索引不同,添加近似索引后,查询结果可能与精确搜索不同。
支持的索引类型:
- HNSW——多层可导航小世界图;
- 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);
| 参数 | 默认值 | 说明 |
|---|---|---|
m | 16 | 每层最大连接数,越大召回率越高但内存占用越多 |
ef_construction | 64 | 构建图的动态候选列表大小,越大建图质量越高但构建越慢 |
查询期参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
hnsw.ef_search | 40 | 搜索动态候选列表大小,越大召回率越高、速度越慢 |
hnsw.iterative_scan | off | 迭代索引扫描模式(strict_order / relaxed_order),提升过滤查询召回率 |
hnsw.max_scan_tuples | 20,000 | 迭代扫描时最大访问元组数 |
hnsw.scan_mem_multiplier | 1 | 迭代扫描内存倍数(基于 work_mem) |
-- 提高召回率
SET hnsw.ef_search = 100;
-- 仅在当前事务内生效(推荐,避免影响其他会话)
BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT ...;
COMMIT;
6.2 IVFFlat 索引
IVFFlat 将向量划分为多个列表(list),查询时只搜索与查询向量最接近的若干列表。其构建速度更快、内存占用更少,但查询性能低于 HNSW(速度-召回率权衡)。
实现良好召回率的三个关键:
- 在表已有数据之后再创建索引(IVFFlat 需要对数据做 k-means 聚类训练);
- 选择合适的 lists 数量:数据不超过 100 万行时,从
行数 / 1000开始;超过 100 万行时,从sqrt(行数)开始; - 查询时指定适当的探测数(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.probes | 1 | 查询探测的列表数;设为 lists 总数时退化为精确搜索 |
ivfflat.iterative_scan | off | 迭代扫描(relaxed_order),提升过滤查询召回率 |
ivfflat.max_probes | — | 迭代扫描时的最大探测数 |
SET ivfflat.probes = 10;
6.3 操作符类对照表
各向量类型在不同距离度量下可用的索引操作符类:
| 距离度量 | vector | halfvec | bit | sparsevec |
|---|---|---|---|---|
| L2 | vector_l2_ops | halfvec_l2_ops | — | sparsevec_l2_ops |
| 内积 | vector_ip_ops | halfvec_ip_ops | — | — |
| 余弦 | vector_cosine_ops | halfvec_cosine_ops | — | sparsevec_cosine_ops |
| L1 | vector_l1_ops | — | — | — |
| 汉明 | — | — | bit_hamming_ops | — |
| Jaccard | — | — | bit_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 依次为initializing→loading tuples;IVFFlat 依次为initializing→performing k-means→assigning tuples→loading tuples。