向量模态
向量模态概述
UDB-TX向量模态是九有推出的企业级向量数据库解决方案,基于九有数据库平台构建,将向量检索能力与传统关系型数据库的ACID特性深度融合。
UDB-TX不仅提供高性能的向量存储与检索功能,还具备与大模型无缝集成的能力,支持多种向量索引算法(IVFFlat、HNSW、StreamingDiskANN)和距离度量方式(欧式距离、余弦相似度、内积等),能够满足从百万级到亿级规模的向量检索需求。
核心特性:
高性能向量检索:支持精确检索和近似检索,提供IVFFlat、HNSW、StreamingDiskANN等多种索引算法,在召回率和查询性能之间提供灵活平衡
丰富的向量类型:支持单精度向量(vector)、半精度向量(halfvec)、二进制向量(bit)、稀疏向量(sparsevec)等多种数据类型
多种距离度量:内置支持欧式距离、余弦相似度、内积、曼哈顿距离、汉明距离等多种相似度计算方法
AI能力集成:支持调用大模型API、自动生成embedding,实现SQL直接调用AI能力
标准SQL接口:使用熟悉的SQL语法进行向量操作,降低学习成本,方便与现有业务系统集成
企业级可靠性:支持ACID事务、WAL日志、主备复制等企业级特性
典型应用场景:
语义搜索:基于文本语义进行智能搜索,而非传统关键词匹配
推荐系统:通过向量相似度计算实现个性化内容推荐
问答系统:结合RAG(检索增强生成)技术,为大模型提供领域知识支持
异常检测:通过向量距离识别异常数据模式
去重与聚类:基于向量相似度进行数据去重和分类聚合
UDB-TX向量模态帮助企业在保留传统数据库优势的同时,轻松获得AI时代所需的向量检索能力,加速智能化应用的开发与落地。
向量引擎
简介
通过存储数据向量并使用向量之间的相似度度量,UDB-TX向量模态可以实现高效、准确的数据搜索和分析。
它具有以下特点:
1.支持多种距离度量: 内置支持多种距离度量,包括欧几里德距离、余弦距离、内积距离和曼哈顿距离。这样的多功能性使得可以根据具体应用需求进行高度定制的基于相似性的搜索和分析。
2.索引支持: 为矢量数据提供高效的索引选项,例如k-最近邻(k-NN)搜索。即使数据集大小增长,用户也可以实现快速查询执行,并保持较高的搜索准确性。
3.易于查询语言访问: 使用我们熟悉的SQL查询语法进行向量操作。这简化了具有SQL知识和经验的用户使用矢量数据库的过程,并避免了学习新的语言或系统。
前提要求
已经安装UDB-TX。
启用向量模态
unvdb=# SELECT ud_enable_component('udb-tv', true);
ud_enable_component
---------------------
(1 row)
验证安装
SELECT name, enabled FROM ud_components WHERE name = 'udb-tv';
name | enabled
--------+---------
udb-tv | t
(1 row)
注意事项
向量数据库利用单精度浮点数的格式进行向量存储,且向量中的元素必须为有限值。
最大支持创建16000个维度的向量。
向量数据类型
UDB-TX支持多种向量数据类型,以适应不同的应用场景。
| 名称 | 维度限制 | 存储空间 | 描述 |
|---|---|---|---|
| vector | 1~16000 | 4 * d + 8 字节 | 单精度浮点向量 |
| halfvec | 1~16000 | 2 * d + 8 字节 | 半精度浮点向量 |
| bit | 1~83886080 | d / 8 + 8 字节 | 二进制向量 |
| sparsevec | 最多16000个非零元素 | 8 * 非零元素数 + 16 字节 | 稀疏向量 |
向量相似的对比方法介绍
-点积 (dot product):向量的点积相似度是指两个向量之间的点积值,它适用于许多实际场景,例如图像识别、语义搜索和文档分类等。但点积相似度算法对向量的长度敏感,因此在计算高维向量的相似性时可能会出现问题。
-内积 (inner product):全称为 Inner Product,是一种计算向量之间相似度的度量算法,它计算两个向量之间的点积(内积),所得值越大越与搜索值相似。
-欧式距离 (L2):直接比较两个向量的欧式距离,距离越近越相似。欧几里得距离算法的优点是可以反映向量的绝对距离,适用于需要考虑向量长度的相似性计算。例如推荐系统中,需要根据用户的历史行为来推荐相似的商品,这时就需要考虑用户的历史行为的数量,而不仅仅是用户的历史行为的相似度。
-余弦相似度 (Cosine):两个向量的夹角越小越相似,比较两个向量的余弦值进行比较,夹角越小,余弦值越大。余弦相似度对向量的长度不敏感,只关注向量的方向,因此适用于高维向量的相似性计算。例如语义搜索和文档分类。
vector 类型
每个 vector 占用 4 * dimensions + 8 bytes 的存储空间。每个元素都是一个单精度浮点数(如real类型),并且所有元素都必须是有限的(非NaN/Infinity/-Infinity)。向量最多可以有 16,000 个维度。
unvdb=# DROP TABLE IF EXISTS items_vector;
unvdb=# CREATE TABLE items_vector (id bigserial PRIMARY KEY, embedding vector(3));
CREATE TABLE
unvdb=# INSERT INTO items_vector (embedding) VALUES ('[1,2,3]');
INSERT 0 1
halfvec 类型(半精度向量)
每个 half vector 占用 2 * dimensions + 8 bytes 的存储空间。每个元素都是一个半精度浮点数,并且所有元素都必须是有限的(非NaN/Infinity/-Infinity)。半精度向量最多可以有 16,000 个维度。
使用 halfvec 类型来存储半精度向量
unvdb=# DROP TABLE IF EXISTS items_halfvec;
unvdb=# CREATE TABLE items_halfvec (id bigserial PRIMARY KEY, embedding halfvec(3));
CREATE TABLE
unvdb=# INSERT INTO items_halfvec (embedding) VALUES ('[1,2,3]');
INSERT 0 1
Bit类型(二进制向量)
使用bit类型存储二进制向量:
unvdb=# DROP TABLE IF EXISTS iteams_bit;
unvdb=# CREATE TABLE iteams_bit (
id BIGSERIAL PRIMARY KEY,
embedding BIT(3)
);
CREATE TABLE
unvdb=# INSERT INTO iteams_bit (embedding) VALUES ('000'), ('111');
INSERT 0 2
按汉明距离查询最近邻:
unvdb=# SELECT * FROM iteams_bit ORDER BY embedding <~> '101' LIMIT 5;
bit类型支持的距离函数:
<~>:汉明距离<%>:Jaccard距离
Sparsevec类型(稀疏向量)
使用sparsevec类型存储稀疏向量:
unvdb=# DROP TABLE IF EXISTS items_spar;
unvdb=# CREATE TABLE items_spar (
id BIGSERIAL PRIMARY KEY,
embedding SPARSEVEC(5));
CREATE TABLE
插入向量(格式:{index:value,...}/dimensions,索引从1开始):
unvdb=# INSERT INTO items_spar (embedding) VALUES
('{1:1,3:2,5:3}/5'),
('{1:4,3:5,5:6}/5');
INSERT 0 2
按L2距离查询最近邻:
unvdb=# SELECT * FROM items_spar
ORDER BY embedding <-> '{1:3,3:1,5:2}/5'
LIMIT 5;
向量操作符
vector 运算符
vector类型支持的方法有欧式距离(L2),内积 (inner product),余弦相似度(Cosine)。支持的向量操作符如下:
| 操作符 | 描述 |
|---|---|
| + | 元素相加 |
| - | 元素相减 |
| * | 元素相乘 |
| || | 连接 |
| <-> | 欧式距离 |
| <#> | 内积 |
| <=> | 余弦距离 |
| <+> | 曼哈顿距离 |
Halfvec 运算符
| 操作符 | 描述 |
|---|---|
| + | 元素相加 |
| - | 元素相减 |
| * | 元素相乘 |
| || | 连接 |
| <-> | 欧式距离 |
| <#> | 内积 |
| <=> | 余弦距离 |
| <+> | 曼哈顿距离 |
bit 运算符
| 算子 | 描述 |
|---|---|
| <~> | 汉明距离 |
| <%> | jaccard距离 |
Sparsevec 运算符
| 算子 | 描述 |
|---|---|
| <-> | 欧式距离 |
| <%> | 负内积 |
| <=> | 余弦距离 |
| <+> | 曼哈顿距离 |
向量函数
vector 函数
| 函数 | 描述 |
|---|---|
| binary_quantize(vector) → bit | 二进制量化 |
| cosine_distance(vector, vector) → double precision | 余弦距离 |
| inner_product(vector, vector) → double precision | 内积 |
| l1_distance(vector, vector) → double precision | 曼哈顿距离 |
| l2_distance(vector, vector) → double precision | 欧式距离 |
| l2_normalize(vector) → vector | 使用欧几里得范数进行归一化 |
| subvector(vector, integer, integer) → vector | 子向量 |
| vector_dims(vector) → integer | 维度数 |
| vector_norm(vector) → double precision | 欧几里得范数 |
Halfvec 函数
| 函数 | 描述 |
|---|---|
| binary_quantize(halfvec) → bit | 二进制量化 |
| cosine_distance(halfvec, halfvec) → double precision | 余弦距离 |
| inner_product(halfvec, halfvec) → double precision | 内积 |
| l1_distance(halfvec, halfvec) → double precision | 曼哈顿距离 |
| l2_distance(halfvec, halfvec) → double precision | 欧式距离 |
| vector_norm(halfvec) → double precision | 欧几里得范数 |
| l2_normalize(halfvec) → halfvec | 使用欧几里得范数进行归一化 |
| subvector(halfvec, integer, integer) → halfvec | 子向量 |
| vector_dims(halfvec) → integer | 维度数 |
bit 函数
| 函数 | 描述 |
|---|---|
| hamming_distance(bit, bit) → double precision | 汉明距离 |
| jaccard_distance(bit, bit) → double precision | jaccard距离 |
Sparsevec 函数
| 函数 | 描述 |
|---|---|
| cosine_distance(sparsevec, sparsevec) → double precision | 余弦距离 |
| inner_product(sparsevec, sparsevec) → double precision | 内积 |
| l1_distance(sparsevec, sparsevec) → double precision | 曼哈顿距离 |
| l2_distance(sparsevec, sparsevec) → double precision | 欧式距离 |
| l2_norm(sparsevec) → double precision | 欧几里得范数 |
| l2_normalize(sparsevec) → sparsevec | 使用欧几里得范数进行归一化 |
向量聚合函数
| 函数 | 描述 |
|---|---|
| avg(vector) → vector | 平均值 |
| sum(vector) → vector | 求和 |
示例:
-- 获得全部向量的平均值
DROP TABLE IF EXISTS item_avg;
CREATE TABLE item_avg (id bigserial PRIMARY KEY, embedding vector(3));
INSERT INTO item_avg (embedding) VALUES ('[1,2,3]');
INSERT INTO item_avg (embedding) VALUES ('[4,5,6]');
SELECT AVG(embedding) FROM item_avg;
-- 获取向量的和
SELECT SUM(embedding) FROM item_avg;
向量存储
创建一个存储向量的表
unvdb=# DROP TABLE IF EXISTS vector_table;
unvdb=# CREATE TABLE vector_table (id bigserial PRIMARY KEY,embedding vector(5));
CREATE TABLE
插入向量
unvdb=# INSERT INTO vector_table (embedding) VALUES ('[1,2,3,4,5]'), ('[0.1,0.2,0.3,0.4,0.5]');
INSERT 0 2
更新向量
unvdb=# INSERT INTO vector_table (id, embedding) VALUES (1, '[1,2,3,4.5,5]'),(2,'[0.1,0.2,0.3,0.4,0.5]')
ON CONFLICT (id) DO UPDATE SET embedding = EXCLUDED.embedding;
INSERT 0 1
更改向量
unvdb=# UPDATE vector_table SET embedding = '[0,0,0,0,0]' WHERE id=1;
UPDATE 1
删除向量
unvdb=# DELETE FROM vector_table WHERE id = 1;
DELETE 1
向量查询
在UDB-TX中,可以使用各种查询运算符对矢量数据进行不同的操作。以下是一些常用的vector查询运算符:
欧式距离
<->:该运算符计算两个向量之间的欧几里德距离。距离值越小越相近。
unvdb=# DROP TABLE IF EXISTS vector_table;
unvdb=# CREATE TABLE vector_table (id bigserial PRIMARY KEY,embedding vector(5));
unvdb=# INSERT INTO vector_table (embedding) VALUES ('[1,2,3,4,5]'), ('[0.1,0.2,0.3,0.4,0.5]');
unvdb=# SELECT embedding <-> '[1,1,1,1,1]' as distance FROM vector_table ORDER BY embedding <-> '[1,1,1,1,1]';
distance
--------------------
1.5968719273367806
5.477225575051661
(2 rows)
余弦距离
<=>:该运算符计算两个向量之间的余弦相似度。两个向量的夹角越小越相似,比较两个向量的余弦值进行比较,夹角越小,余弦值越大。
unvdb=# SELECT embedding <=> '[1,1,1,1,1]' as similarity FROM vector_table ORDER BY embedding <-> '[1,1,1,1,1]' DESC;
similarity
---------------------
0.09546596626670922
0.0954659760693326
(2 rows)
内积
<#>:该运算符计算两个向量之间的内积。值越大越与搜索值相近。
unvdb=# SELECT embedding <#> '[1,1,1,1,1]' as distance FROM vector_table ORDER BY embedding <-> '[1,1,1,1,1]';
distance
----------
-1.5
-15
(2 rows)
在选择适当的运算符时,您应该考虑您的应用需求和数据特性。这可能涉及保持相对距离、强调大小或方向以及关注特定维度等因素。请注意,根据您的数据和用例,运算符的选择可能会对搜索结果的质量以及最终应用程序的有效性产生重大影响。
下面是根据距离已经确定的运算符进行向量查询的例子:
查询最近邻向量:
unvdb=# SELECT * FROM vector_table ORDER BY embedding <-> '[1,1,1,1,1]' LIMIT 1;
id | embedding
----+-----------------------
2 | [0.1,0.2,0.3,0.4,0.5]
(1 row)
按照距离范围进行向量筛选:
unvdb=# SELECT * FROM vector_table WHERE embedding <-> '[1,1,1,1,1]' < 5;
id | embedding
----+-----------------------
2 | [0.1,0.2,0.3,0.4,0.5]
(1 row)
平均向量
获得全部向量的平均值
unvdb=# SELECT AVG(embedding) FROM vector_table;
avg
--------------------------
[0.55,1.1,1.65,2.2,2.75]
(1 row)
获取向量的平均组数
unvdb=# SELECT id, AVG(embedding) FROM vector_table GROUP BY id LIMIT 5;
id | avg
----+-------------
1 | [1,2,3,4,5]
2 | [0.1,0.2,0.3,0.4,0.5]
(2 rows)
向量索引
默认情况下,UDB-TX执行精确最近邻搜索,从而满足最高的召回率。
您可以添加索引来使用近似最近邻搜索,该搜索方式以牺牲一些召回率的方式换取搜索速度的提升。与典型索引不同,添加近似索引后,您将看到不同的查询结果。
支持的索引类型包括:
IVFFlat
HNSW
StreamingDiskANN
| 索引类型 | 算法 | 适用场景 | 特点 |
|---|---|---|---|
| HNSW | 分层导航小世界图 | 高性能查询 | 查询速度快,召回率高,内存占用大 |
| IVFFlat | 倒排文件索引 | 中等规模数据 | 构建快,内存占用小,需调参 |
| StreamingDiskANN | 磁盘图索引 | 超大规模数据 | 低内存占用,SSD友好,高吞吐 |
索引维度限制:
vector类型建索引最多支持2,000维
halfvec类型建索引最多支持4,000维
bit类型建索引最多支持64,000维
sparsevec类型建索引最多支持1,000个非零元素
IVFFlat
它的工作原理是将相似的向量聚类为区域,并建立一个倒排索引,将每个区域映射到其向量。这使得查询可以集中在数据的一个子集上,从而实现快速搜索。 通过调整列表和探针参数,ivfflat可以平衡数据集的速度和准确性,使UDB-TX有能力对复杂数据进行快速的语义相似性搜索。
通过简单的查询,应用程序可以在数百万个高维向量中找到与查询向量最近的邻居。对于自然语言处理、信息检索等,ivfflat是一个比较好的解决方案。 在建立ivfflat索引时,你需要决定索引中包含多少个list,每个list代表一个”中心”;这些中心通过k-means算法计算而来。一旦确定了所有中心,ivfflat就会确定每个向量最靠近哪个中心,并将其添加到索引中。
当需要查询向量数据时,你可以决定要检查多少个中心,这由ivfflat.probes参数决定。这就是ANN性能/召回率的结果:访问的中心越多,结果就越精确,但这是以牺牲性能为代价的。 与HNSW相比,它具有更快的构建速度并使用更少的内存,但查询性能较差(在速度和召回率权衡方面)。
采用ivfflat找到近似的近邻,该算法的操作假设是最近的向量将位于与查询向量相同的区域。ivfflat采用了以下步骤:
计算查询向量与索引中每个中心点之间的距离
选择距离最小的中心点作为离查询最近的中心点
从倒排索引中检索与最近中心点对应的区域关联的向量
计算查询向量与检索集中每个向量之间的距离
选择距离最小的K个向量作为查询的近似最近的邻居
索引设置
以下是一些关于IVFFlat索引的建议:
在表中有一定数量的数据后创建索引:在创建索引之前,确保表中有足够的数据,以便索引能够提供更好的查询性能。
选择适当数量的列表:可以根据表的大小来选择适当数量的列表。一般来说,可以使用表的行数除以 1000(最多1M行)和平方根(rows)(超过1M行)作为起点。
指定适当的探针数量:在执行查询时,可以指定适当的探针数量来平衡查询速度和召回率。一般来说,可以使用列表数量除以 10(最多1M行)和平方根(lists)(超过1M行)作为起点。
这些建议可以帮助您在近似最近邻搜索中获得良好的准确性和性能。请注意,具体的索引配置可能需要根据您的数据和查询需求进行调整,以达到最佳性能。
要使用的每个距离函数添加一个索引,请参考下面的用例:
欧式距离
unvdb=# DROP TABLE IF EXISTS vector_table;
unvdb=# CREATE TABLE vector_table (id bigserial PRIMARY KEY,embedding vector(5));
unvdb=# INSERT INTO vector_table (embedding) VALUES ('[1,2,3,4,5]'), ('[0.1,0.2,0.3,0.4,0.5]');
unvdb=# CREATE INDEX ON vector_table USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
NOTICE: ivfflat index created with little data
DETAIL: This will cause low recall.
HINT: Drop the index until the table has more data.
CREATE INDEX
内积
unvdb=# CREATE INDEX ON vector_table USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);
NOTICE: ivfflat index created with little data
DETAIL: This will cause low recall.
HINT: Drop the index until the table has more data.
CREATE INDEX
余弦距离
unvdb=# CREATE INDEX ON vector_table USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
NOTICE: ivfflat index created with little data
DETAIL: This will cause low recall.
HINT: Drop the index until the table has more data.
CREATE INDEX
汉明距离
unvdb=# DROP TABLE IF EXISTS bit_table;
unvdb=# CREATE TABLE bit_table (id bigserial PRIMARY KEY,embedding bit(3));
unvdb=# CREATE INDEX ON bit_table USING ivfflat (embedding bit_hamming_ops) WITH (lists = 100);
CREATE INDEX
支持的类型包括:
vector- 支持最多 2,000 个维度的向量建立索引
halfvec- 支持最多 4,000 个维度的向量建立索引
bit- 支持最多 64,000 个维度的向量建立索引
lists参数表示将数据集分成的列表数,该值越大,表示数据集被分割得越多,每个子集的大小相对较小,索引查询速度越快。但随着lists值的增加,查询的召回率可能会下降。
召回率是指在信息检索或分类任务中,正确检索或分类的样本数量与所有相关样本数量之比。召回率衡量了系统能够找到所有相关样本的能力,它是一个重要的评估指标。您需要通过调整lists参数的值,在查询速度和召回率之间进行权衡,以满足具体应用场景的需求。
查询设置
您可以使用如下两种方式之一来设置ivfflat.probes参数,指定在索引中搜索的列表数量,通过增加ivfflat.probes的值,将搜索更多的列表,可以提高查询结果的召回率,即找到更多相关的结果。
会话级别
设定探针数量(默认为1)
SET ivfflat.probes = 10;
事务级别
BEGIN;
SET LOCAL ivfflat.probes = 10;
SELECT ...
COMMIT;
ivfflat.probes的值越大,查询结果的召回率越高,但是查询的速度会降低,根据具体的应用需求和数据集的特性,lists和ivfflat.probes的值可能需要进行调整以获得最佳的查询性能和召回率。
HNSW
HNSW(Hierarchical Navigable Small World)是一种基于图的索引算法,它由多层的邻近图组成,因此称为分层的NSW方法。它会为一张图按规则建成多层导航图,并让越上层的图越稀疏,结点间的距离越远;越下层的图越稠密,结点间的距离越近。
HNSW算法是一种经典的空间换时间的算法,它的搜索质量和搜索速度都比较高,但是它的内存开销也比较大,因为不仅需要将所有的向量都存储在内存中,还需要维护一个图的结构,也同样需要存储。所以这类算法需要根据实际的场景来选择。
HNSW的原理如下:
构建层级索引:首先,将数据集中的点随机选择一个作为入口点(Entry Point),然后将其他点插入到索引中形成多层级的结构。每一层都是一个Small World网络,其中每个点都连接到其他几个点,以便在搜索时能够速定位到更接近目标的点。
贪心搜索:搜索时,从最高层级(Entry Point所在层级)开始,根据一定的规则选择相邻点,直到达到目标点或者到达最低层级。在每一层级中,选择与目标点更接近的点作为下一层级的起点,并继续向下搜索。
剪枝和修剪:在搜索过程中,可以使用剪枝和修剪策略来减少搜索空间。剪枝是指通过一些阈值或规则判断某些节点是否需要继续扩展搜索,如果不需要,则直接跳过这些节点。修剪是指在搜索过程中,根据实时计算的距离等信息,将一些不相关或不接近目标的节点从搜索路径中移除,以加快搜索速度。
近似最近邻:由于HNSW是一种近似搜索算法,所以找到的最近邻并不一定是精确的最近邻。但是,HNSW能够在高维空间中以较小的误差找到近似的最近邻,同时具有较高的搜索效率。
HNSW 索引可创建多层图结构。它的构建时间较慢,使用比IVFFlat更多的内存,但具有更好的查询性能(在速度和召回率权衡方面)。HNSW没有像IVFFlat这样的训练步骤,因此可以在没有任何数据的情况下创建索引。
索引设置
指定HNSW参数
m- 每层的最大连接数(默认为 16)ef_construction- 用于构造图形的动态候选列表的大小(默认为 64)
unvdb=# DROP TABLE IF EXISTS vector_table;
unvdb=# CREATE TABLE vector_table (id bigserial PRIMARY KEY,embedding vector(5));
unvdb=# INSERT INTO vector_table (embedding) VALUES ('[1,2,3,4,5]'), ('[0.1,0.2,0.3,0.4,0.5]');
unvdb=# CREATE INDEX ON vector_table USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
CREATE INDEX
要使用的每个距离函数添加一个索引,请参考下面的用例:
欧式距离
unvdb=# CREATE INDEX ON vector_table USING hnsw (embedding vector_l2_ops);
CREATE INDEX
内积
unvdb=# CREATE INDEX ON vector_table USING hnsw (embedding vector_ip_ops);
CREATE INDEX
余弦距离
unvdb=# CREATE INDEX ON vector_table USING hnsw (embedding vector_cosine_ops);
CREATE INDEX
汉明距离
unvdb=# DROP TABLE IF EXISTS bit_table;
unvdb=# CREATE TABLE bit_table (id bigserial PRIMARY KEY,embedding bit(3));
unvdb=# CREATE INDEX ON bit_table USING hnsw (embedding bit_hamming_ops);
CREATE INDEX
Jaccard距离
unvdb=# DROP TABLE IF EXISTS bit_table;
unvdb=# CREATE TABLE bit_table (id bigserial PRIMARY KEY,embedding bit(3));
unvdb=# CREATE INDEX ON bit_table USING hnsw (embedding bit_jaccard_ops);
CREATE INDEX
支持的类型包括:
vector- 支持最多 2,000 个维度的向量建立索引
halfvec- 支持最多 4,000 个维度的向量建立索引
bit- 支持最多 64,000 个维度的向量建立索引
sparsevec- 支持最多 1,000 个非零元素维度的向量建立索引
查询设置
指定用于搜索的动态候选列表的大小(默认为 40),设定较高的值会以牺牲速度为代价得到更好的召回率。
会话级别
SET hnsw.ef_search = 100;
事务级别
BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT ...
COMMIT;
StreamingDiskANN索引
StreamingDiskANN是一种高性能磁盘索引
索引设置
基本语法:
CREATE INDEX [索引名]
ON [表名]
USING diskann (列名 操作符类)
示例:
-- 创建一个包含向量列的表
unvdb=# DROP TABLE IF EXISTS document_embedding;
unvdb=# CREATE TABLE document_embedding (
id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
metadata JSONB,
contents TEXT,
embedding VECTOR(5)
);
-- 向表中插入数据
unvdb=# INSERT INTO document_embedding (metadata, contents, embedding) VALUES
('{"author": "张三", "tags": ["科技"]}', '人工智能概述', '[0.1, 0.2, 0.3, 0.4, 0.5]'),
('{"author": "李四", "tags": ["文学"]}', '古典诗词赏析', '[0.9, 0.8, 0.7, 0.6, 0.5]'),
('{"author": "王五", "tags": ["科技"]}', '机器学习基础', '[0.15, 0.25, 0.35, 0.45, 0.55]'),
('{"author": "赵六", "tags": ["历史"]}', '中国古代史', '[0.8, 0.7, 0.6, 0.5, 0.4]');
-- 创建StreamingDiskANN 索引
unvdb=# CREATE INDEX document_embedding_idx ON document_embedding
USING diskann (embedding vector_cosine_ops);
-- 执行相似性搜索
unvdb=# SELECT *
FROM document_embedding
ORDER BY embedding <=> '[0.12, 0.22, 0.32, 0.42, 0.52]'
LIMIT 3;
支持的操作符:
vector_l2_ops:L2距离vector_ip_ops:内积vector_cosine_ops:余弦距离
半精度索引(Half-Precision Indexing)
半精度索引是为了提供较小的索引
unvdb=# DROP TABLE IF EXISTS vector_table;
unvdb=# CREATE TABLE vector_table (id bigserial PRIMARY KEY,embedding vector(3));
unvdb=# INSERT INTO vector_table (embedding) VALUES ('[1,2,3]'), ('[0.1,0.2,0.3]');
unvdb=# CREATE INDEX ON vector_table USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops);
CREATE INDEX
获取最近的邻
unvdb=# SELECT * FROM vector_table ORDER BY embedding::halfvec(3) <-> '[1,2,3]' LIMIT 2;
id | embedding
----+-----------
1 | [1,2,3]
2 | [0.1,0.2,0.3]
(2 rows)
Filtering(过滤)机制
有几种方法可以使用WHERE子句为最近邻查询建立索引。
unvdb=# DROP TABLE IF EXISTS vector_table;
unvdb=# CREATE TABLE vector_table (id bigserial PRIMARY KEY,embedding vector(3));
unvdb=# INSERT INTO vector_table (embedding) VALUES ('[1,2,3]'), ('[0.1,0.2,0.3]');
unvdb=# SELECT * FROM vector_table WHERE id = 1 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
id | embedding
----+-------------
1 | [1,2,3]
(1 row)
一个好的方法是在需要过滤的列上建立索引。在许多情况下,这可以提供快速、准确的最近邻搜索。UDB-TX为此提供了多种索引类型:B 树(默认)、哈希、GiST、SP-GiST、GIN 和 BRIN。
unvdb=# CREATE INDEX ON vector_table (id);
CREATE INDEX
对于多列,请考虑混合索引。
unvdb=# CREATE INDEX ON vector_table (id, embedding);
CREATE INDEX
精确索引适用于与较低百分比的行匹配的条件。否则,近似索引可以更好地工作。
unvdb=# CREATE INDEX ON vector_table USING hnsw (embedding vector_l2_ops);
CREATE INDEX
对于近似索引,将在扫描索引后应用筛选。如果条件匹配 10% 的行,使用 HNSW 和默认值 40,则平均只有 4 行匹配。对于更多行,请增加 hnsw.ef_search。
unvdb=# SET hnsw.ef_search = 200;
SET
您可以启用迭代索引扫描,这将在需要时自动扫描更多索引。
unvdb=# SET hnsw.iterative_scan = strict_order;
SET
如果仅按几个不同的值进行筛选,请考虑使用部分索引。
unvdb=# CREATE INDEX ON vector_table USING hnsw (embedding vector_l2_ops) WHERE (id = 123);
CREATE INDEX
如果按许多不同的值进行筛选,请考虑分区。
unvdb=# DROP TABLE IF EXISTS vector_table;
unvdb=# CREATE TABLE vector_table (embedding vector(5), id int) PARTITION BY LIST(id);
CREATE INDEX
迭代索引扫描(Iterative Index Scans)
对于近似索引,使用筛选的查询可能返回更少的结果,因为筛选是在扫描索引后应用的。您可以启用迭代索引扫描,它将自动扫描更多索引,直到找到足够的结果。
迭代扫描可以使用严格或宽松排序。
Strict 确保结果按距离准确排序
unvdb=# SET hnsw.iterative_scan = strict_order;
SET
“Relaxed”允许结果按距离略微乱序,但提供更好的召回率
unvdb=# SET hnsw.iterative_scan = relaxed_order;
SET
# or
unvdb=# SET ivfflat.iterative_scan = relaxed_order;
SET
使用宽松排序,您可以使用 materialized CTE 来获得严格的排序
unvdb=# DROP TABLE IF EXISTS iterative_table;
unvdb=# CREATE TABLE iterative_table (id bigserial PRIMARY KEY,embedding vector(3));
UNVDB=# INSERT INTO iterative_table (embedding) VALUES ('[1,2,3]'),('[1,2,1]');
unvdb=# WITH relaxed_results AS MATERIALIZED (
SELECT id, embedding <-> '[1,2,3]' AS distance FROM iterative_table ORDER BY distance LIMIT 5
) SELECT * FROM relaxed_results ORDER BY distance;
id | distance
----+-------------------
1 | 0
2 | 2
(2 row)
对于按距离筛选的查询,请使用 materialized CTE 并将距离筛选器放在其外部以获得最佳性能
unvdb=# WITH nearest_results AS MATERIALIZED (
SELECT id, embedding <-> '[1,2,3]' AS distance FROM iterative_table ORDER BY distance LIMIT 5
) SELECT * FROM nearest_results WHERE distance < 5 ORDER BY distance;
id | distance
----+-----------------
1 | 0
2 | 2
(2 rows)
注意:将任何其他过滤器放在 CTE 中
迭代扫描选项
由于扫描近似索引的大部分成本很高,因此可以选择选项来控制何时结束扫描。
HNSW
指定要访问的最大元组数(默认为 20,000)
unvdb=# SET hnsw.max_scan_tuples = 20000;
SET
注: 这是近似值,不会影响初始扫描
指定要使用的最大内存量,作为 work_mem(默认为 1) 的倍数
unvdb=# SET hnsw.scan_mem_multiplier = 2;
SET
注意:如果没有提高召回率,请尝试增加 hnsw.max_scan_tuples 的值
IVFFlat
指定最大探测数
unvdb=# SET ivfflat.max_probes = 100;
SET
注意:如果此值低于ivfflat.probes,将使用 ivfflat.probes
索引子向量(Indexing Subvectors)
使用表达式索引为子向量建立索引
unvdb=# DROP TABLE IF EXISTS items;
unvdb=# CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(5));
unvdb=# INSERT INTO items (embedding) VALUES ('[1,2,3,4,5]');
unvdb=# INSERT INTO items (embedding) VALUES ('[0.1,0.2,0.3,0.4,0.5]');
unvdb=# CREATE INDEX ON items USING hnsw ((subvector(embedding, 1, 3)::vector(3)) vector_cosine_ops);
CREATE INDEX
按余弦距离获取最近邻
unvdb=# SELECT * FROM items ORDER BY subvector(embedding, 1, 3)::vector(3) <=> subvector('[1,2,3,4,5]'::vector, 1, 3) LIMIT 5;
id | embedding
----+-----------------------
1 | [1,2,3,4,5]
2 | [0.1,0.2,0.3,0.4,0.5]
(2 rows)
按完整向量重新排序,以便更好地调用
unvdb=# SELECT * FROM (
SELECT * FROM items ORDER BY subvector(embedding, 1, 3)::vector(3) <=> subvector('[1,2,3,4,5]'::vector, 1, 3) LIMIT 20
) ORDER BY embedding <=> '[1,2,3,4,5]' LIMIT 5;
id | embedding
----+-----------------------
1 | [1,2,3,4,5]
2 | [0.1,0.2,0.3,0.4,0.5]
(2 rows)
过滤向量搜索
支持将向量相似性搜索与元数据过滤相结合。主要提供两种过滤方式,可在同一查询中组合使用:
1. 基于标签的索引过滤(diskann索引): 通过标签实现高性能过滤,基于微软研究的Filtered DiskANN方法开发,在保证高召回率的同时实现高效过滤。
2. 任意WHERE条件过滤(后过滤): 在向量搜索完成后进行过滤,虽然速度较慢,但采用流式处理确保准确性,无需将全部结果加载到内存。
基于diskann索引的标签过滤
创建包含标签数组的表
DROP TABLE IF EXISTS documents;
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
embedding VECTOR(10), -- 10维向量(测试用简化版)
labels SMALLINT[], -- 标签数组(smallint范围)
status TEXT,
created_at TIMESTAMPTZ
);
插入数据
INSERT INTO documents (embedding, labels, status, created_at) VALUES
('[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]', ARRAY[1], 'active', '2024-01-15'),
('[0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95, 1.0]', ARRAY[2], 'active', '2024-02-20'),
('[0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0]', ARRAY[3], 'inactive', '2024-03-10');
创建包含标签列的索引
CREATE INDEX ON documents USING diskann (embedding vector_cosine_ops, labels);
执行标签过滤查询
SELECT * FROM documents
WHERE labels && ARRAY[1, 3]::smallint[]
ORDER BY embedding <=> '[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]'
LIMIT 10;
为标签赋予语义 通过关联表将整数ID映射为有意义的标签名称,兼顾性能与可读性。
创建标签定义表
DROP TABLE IF EXISTS label_definitions;
CREATE TABLE label_definitions (
id INTEGER PRIMARY KEY, -- 标签ID(必须与documents表中的labels数组类型匹配)
name TEXT,
description TEXT,
attributes JSONB -- 扩展属性(如置信度、领域等)
);
插入数据
INSERT INTO label_definitions (id, name, description, attributes) VALUES
(1, '科技', '科技类内容', '{"color": "blue"}'),
(2, '财经', '财经类内容', '{"color": "green"}'),
(3, '体育', '体育类内容', '{"color": "red"}');
插入文档数据时使用整数ID
INSERT INTO documents (embedding, labels)
VALUES ('[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]', ARRAY[1, 2]);
查询时实现语义化
-- 方法A:直接显示标签名称
SELECT
d.*,
array_agg(l.name) AS tag_names -- 将[1,2]转换为["科技","财经"]
FROM documents d
JOIN label_definitions l ON l.id = ANY(d.labels)
GROUP BY d.id;
-- 方法B:通过标签名称过滤
SELECT * FROM documents
WHERE labels && (
SELECT array_agg(id::smallint)
FROM label_definitions
WHERE name IN ('科技', '财经') -- 先查名称对应的ID
);
任意WHERE条件过滤
还可以将任何 WHERE 子句与向量搜索一起使用,但这些条件将作为后过滤应用,对高频过滤条件建议改用标签索引方案。
SELECT * FROM documents
WHERE status = 'active' AND created_at > '2024-01-01'
ORDER BY embedding <=> '[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]'
LIMIT 10;
常见问题解答
单个表中可以存储多少个 vector?
默认情况下,非分区表在 unvdb 中的限制为 32 TB。一个分区表可以有数千个该大小的分区。
是否支持复制?
是的,通过使用预写日志 (WAL),它允许复制和时间点恢复。
如果要为超过 2,000 个维度的向量编制索引,该怎么办?
您可以使用半精度索引,多达 4000 个维度创建索引;或使用二进制量化,多达 64000 个维度创建索引。另一种选择是降维。
我可以在同一列中存储不同维度的向量吗?
您可以使用vector而不是vector(3)
unvdb=# CREATE TABLE embeddings (model_id bigint, item_id bigint, embedding vector, PRIMARY KEY (model_id, item_id));
CREATE TABLE
但是,您只能在具有相同维度数的行上创建索引.
unvdb=# CREATE INDEX ON embeddings USING hnsw ((embedding::vector(3)) vector_l2_ops) WHERE (model_id = 123);
CREATE INDEX
并使用以下命令进行查询:
unvdb=# SELECT * FROM embeddings WHERE model_id = 123 ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
我能否更精确地存储向量?
您可以使用double precision[] 或 numeric[]更精确地存储向量。
DROP TBALE IF EXISTS items;
CREATE TABLE items (id bigserial PRIMARY KEY, embedding double precision[]);
-- use {} instead of [] for UDB-TX arrays
INSERT INTO items (embedding) VALUES ('{1,2,3}'), ('{4,5,6}');
(可选)添加 check 约束以确保数据可以转换为vector类型并具有预期的维度。
ALTER TABLE items ADD CHECK (vector_dims(embedding::vector) = 3);
使用表达式索引进行索引(精度较低):
CREATE INDEX ON items USING hnsw ((embedding::vector(3)) vector_l2_ops);
并使用以下命令进行查询:
SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
索引需要适合的内存吗?
不需要,但与其他索引类型一样,如果它们这样做,您可能会看到更好的性能。您可以通过以下方式获取索引的大小:
SELECT pg_size_pretty(pg_relation_size('index_name'));
为什么查询不使用索引?
查询需要具有ORDER BY and LIMIT,并且 ORDER BY 必须是按升序排列的距离运算符(不是表达式)的结果。
-- index
ORDER BY embedding <=> '[3,1,2]' LIMIT 5;
-- no index
ORDER BY 1 - (embedding <=> '[3,1,2]') DESC LIMIT 5;
您可以鼓励 Planner 使用索引来查询:
BEGIN;
SET LOCAL enable_seqscan = off;
SELECT ...
COMMIT;
此外,如果表较小,则表扫描可能会更快。
为什么不使用并行表的查询进行扫描?
规划器在成本估算中不考虑离线存储,这可能会使串行扫描看起来更便宜。您可以通过以下方式降低查询的并行扫描成本:
BEGIN;
SET LOCAL min_parallel_table_scan_size = 1;
SET LOCAL parallel_setup_cost = 1;
SELECT ...
COMMIT;
或选择内联存储向量:
ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;
为什么在添加 HNSW 索引后,查询的结果较少?
结果受动态候选列表大小的限制。由于查询中的死元组或筛选条件,结果可能会更少。我们建议设置为至少是查询的两倍。如果您需要的结果超过 500 个,请改用 IVFFlat 索引。
另外,请注意,NULL向量没有索引(余弦距离中的零向量)。
为什么在添加 IVFFlat 索引后,查询的结果较少?
该索引可能是使用对于列表数量来说太少的数据创建的。删除索引,直到表有更多数据。
DROP INDEX index_name;
结果也可能受到探针数量的限制(ivfflat.probes)。 另外,请注意,零向量没有索引(余弦距离中的零向量)。
使用示例
问题提出
构建一个测试用例,在UDB-TX中测试对向量数据的检索。向量数据集采用公开的国内省市位置数据,将经纬度作为向量维度存储。通过欧几里德距离计算向量数据间距离(即城市间距离),查询距离北京最近的城市。
建表
unvdb=# DROP TABLE IF EXISTS cities;
unvdb=# create table cities(id bigserial primary key,province text,city text,pos vector(2));
CREATE TABLE
插入数据
unvdb=# insert into cities values(1,'北京','北京市','[39.90469,116.40717]');
INSERT 0 1
unvdb=# insert into cities values(2,'天津','天津市','[39.0851,117.19937]');
INSERT 0 1
unvdb=# insert into cities values(3,'上海','上海市','[31.23037,121.4737]');
INSERT 0 1
unvdb=# insert into cities values(4,'重庆','重庆市','[29.56471,106.55073]');
INSERT 0 1
unvdb=# insert into cities values(5,'香港特别行政区','九龙','[22.327114,114.17495]');
INSERT 0 1
创建向量索引
unvdb=# create index on cities using ivfflat (pos vector_l2_ops) with (lists = 100);
NOTICE: ivfflat index created with little data
DETAIL: This will cause low recall.
HINT: Drop the index until the table has more data.
CREATE INDEX
查看数据
unvdb=# select * from cities;
id | province | city | pos
----+----------------+--------+-----------------------
1 | 北京 | 北京市 | [39.90469,116.40717]
2 | 天津 | 天津市 | [39.0851,117.19937]
3 | 上海 | 上海市 | [31.23037,121.4737]
4 | 重庆 | 重庆市 | [29.56471,106.55073]
5 | 香港特别行政区 | 九龙 | [22.327114,114.17495]
(5 rows)
计算近似向量,查询距离北京最近的城市
unvdb=# select province,city,pos,pos<->(select pos from cities where province='北京' and city='北京市') as distance from cities order by pos<->(select pos from cities where province='北京' and city='北京市') limit 5;
province | city | pos | distance
----------------+--------+-----------------------+--------------------
北京 | 北京市 | [39.90469,116.40717] | 0
天津 | 天津市 | [39.0851,117.19937] | 1.1398720542316627
上海 | 上海市 | [31.23037,121.4737] | 10.045572958859905
重庆 | 重庆市 | [29.56471,106.55073] | 14.285121000776854
香港特别行政区 | 九龙 | [22.327114,114.17495] | 17.71874655533476
(5 rows)