# 向量搜索
向量搜索是一种将数据表示为向量的搜索方法。它通常用于图像搜索、视频搜索、文本搜索等应用,以及图像分类和聚类等机器学习应用。
本节介绍如何创建和操作向量索引以加速向量搜索,以及如何配置不同类型的向量索引。
# 命令参考
# 创建带有向量的表
目前,我们支持两种类型的向量:float32 数组和二进制字符串。相应地,在 MyScale 中,嵌入向量将表示为 Array(Float32)
或 FixedString
。
注意
二进制字符串类型的向量仅适用于数据库版本 1.3.1 或更高版本。
提示
嵌入向量列的所有向量 必须 具有相同的维度。
- 对于 float32 数组向量,请使用
CONSTRAINT
来避免错误。例如,CONSTRAINT constraint_name_1 CHECK length(data) = 128
。 - 对于二进制字符串向量,请将它们表示为固定长度的
FixedString(N)
类型。因此,不必为这些向量指定维度约束,因为每个向量的维度必须保持一致且等于8 * N
。
以下是为不同类型的向量数据创建表的示例。
-- 创建一个表格,其中包含一个128维的 float32 向量数组和一个具有长度约束的向量列
CREATE TABLE test_vector
(
id UInt32,
data Array(Float32),
CONSTRAINT check_length CHECK length(data) = 128,
date Date,
label Enum8('person' = 1, 'building' = 2, 'animal' = 3)
) ENGINE = MergeTree ORDER BY id
-- 创建一个表格,包含128维(16字节)的二进制字符串向量
CREATE TABLE test_binary_vector
(
id UInt32,
binary_data FixedString(16),
date Date,
label Enum8('person' = 1, 'building' = 2, 'animal' = 3)
) ENGINE = MergeTree ORDER BY id
注意
数据类型名称区分大小写,如果大小写错误,将返回错误。
# 创建向量索引
在运行向量搜索之前,必须先创建向量索引。创建向量索引的语法如下:
ALTER TABLE [db.]table_name
ADD VECTOR INDEX index_name column_name
[TYPE index_type('param1 = value1', 'param2 = value2', ...)]
index_name
: 向量索引的名称。column_name
: 将创建向量索引的列的名称。此列必须是Array(Float32)
或FixedString
类型。当它是Array(Float32)
类型时,必须有一个约束来指定数组的长度。index_type
: 向量索引的具体类型。
提示
对于 float32 向量数组,我们强烈建议使用 MSTG
算法以获得最佳结果。然而,以下其他索引类型也可用。
FLAT
: 用于比较的暴力算法ScaNN
: 由 Google Research 开发的可扩展最近邻算法IVF
系列:包括IVFFLAT
、IVFSQ
和IVFPQ
HNSW
系列:包括HNSWFLAT
、HNSWSQ
和HNSWPQ
对于二进制字符串向量,可用的索引类型为 BinaryMSTG
、BinaryFLAT
、BinaryIVF
和 BinaryHNSW
。在这些中,我们强烈推荐使用 BinaryMSTG
算法以获得最佳结果。
NOTE
MSTG
和 BinaryMSTG
的向量索引类型仅在 SaaS/BYOC 版本中提供,而所有其他类型都在所有版本中提供,包括开源、SaaS 和 BYOC。
还有两种方法可以创建默认的向量索引,而无需指定 index_type
:
- 创建一个
index_type
等于DEFAULT
的向量索引。 - 在创建向量索引时,无需指定
TYPE index_type(...)
字段。
提示
在开源版本中,我们默认创建 ScaNN
和 BinaryIVF
向量索引。而在 SaaS/BYOC 版本中,默认创建 MSTG
和 BinaryMSTG
向量索引。
例如,要在 SaaS 上为 test_float_vector
表中的 vector
列创建一个名为 idx
类型为 MSTG
的向量索引,请使用以下命令之一:
-- 指定向量索引类型。
ALTER TABLE test_float_vector ADD VECTOR INDEX idx vector TYPE MSTG
-- 创建一个index_type等于DEFAULT的向量索引。
ALTER TABLE test_float_vector ADD VECTOR INDEX idx vector TYPE DEFAULT
-- 未指定向量索引类型。
ALTER TABLE test_float_vector ADD VECTOR INDEX idx vector
提示
有关特定类型的参数详细信息,请参阅向量索引配置选项说明部分。有关性能调优建议,请参阅性能调优建议部分。
从 DB 版本 v1.4.0 或更高版本开始,MyScale 允许在表上创建多个向量索引。但是,在一个向量列上只允许创建一个向量索引。例如,可以创建一个具有多个向量列的表:
-- 创建具有两个不同维度的float32向量列的表
CREATE TABLE test_multiple_vectors
(
id UInt32,
vec1 Array(Float32),
vec2 Array(Float32),
CONSTRAINT check_length CHECK length(vec1) = 128,
CONSTRAINT check_length CHECK length(vec2) = 64,
date Date,
label Enum8('person' = 1, 'building' = 2, 'animal' = 3)
) ENGINE = MergeTree ORDER BY id
然后可以在每个向量列上创建一个向量索引:
-- 在 vec1 列上添加 MSTG 索引类型
ALTER TABLE test_multiple_vectors ADD VECTOR INDEX idx1 vec1 TYPE MSTG
-- 在 vec2 列上添加另一个 MSTG 向量索引
ALTER TABLE test_multiple_vectors ADD VECTOR INDEX idx2 vec2 TYPE MSTG
# 删除向量索引
可以使用以下语法删除向量索引。它将删除索引并释放相关的内存和磁盘资源。如果索引当前正在构建中,则构建过程也将立即停止。
ALTER TABLE [db.]table_name DROP VECTOR INDEX index_name
# 检查向量索引的状态
要查看向量索引的当前状态,可以使用 system.vector_indices
系统表。以下语法允许您查看所有现有的向量索引:
SELECT * FROM system.vector_indices
您可以使用 WHERE 子句按表名或其他条件筛选结果。例如,要查看特定表(如 test_float_vector
)的向量索引,可以使用以下命令:
SELECT table, name, status FROM system.vector_indices
WHERE table = 'test_float_vector'
这将输出有关向量索引的信息,包括其当前状态,可能的状态有以下几种:
Built
:此状态表示索引已成功构建并准备就绪。InProgress
:此状态表示索引当前正在构建或更新。在此期间,索引可能不完整,未被索引的向量搜索将回退到蛮力算法,速度较慢。Error
:如果索引在构建或使用过程中遇到错误,将进入Error
状态。这可能是由于各种原因,如无效的输入数据或系统故障。当索引处于此状态时,通常无法使用,直到错误解决为止。
对于处于 Error
状态的向量索引,可以使用以下命令查看失败原因:
SELECT table, name, latest_failed_part, latest_fail_reason
FROM system.vector_indices WHERE status = 'Error'
# 基本向量搜索
在 MyScale 中,使用 distance()
函数执行向量搜索。它计算指定向量与指定列中的所有向量数据之间的距离,并返回前几个候选结果。distance()
函数的基本语法如下:
distance('param1 = value1', 'param2 = value2')(column_name, query_vector)
params
表示特定于搜索的参数。params
还可以包括特定于索引的参数,例如nprobe = 1
(对于IVFFLAT
向量索引)来定义搜索范围。column_name
是要搜索的包含向量数据的列的名称。query_vector
是将要被搜索的向量。
提示
- 对于float32向量数组,重要的是要在查询向量中包含一个小数点,以防止它被识别为
Array(UInt64)
类型,这将导致执行查询时出现错误。例如,一个128D的float32向量数组的格式为[3.0, 9, ..., 4]
。 - 对于二进制字符串向量,可能不像其他类型那样容易显示和输入,但我们可以借助特定函数来操作它。
- 使用
char()
函数产生一个二进制向量。这个函数返回二进制字符串,其长度为传递参数的数量,每个字节的值为相应参数的值。例如,一个128D的二进制字符串向量的格式为char(128, 254, ... 127, 100)
。 - 使用
unbin()
函数产生一个二进制向量。这个函数将参数中的每一对二进制数字解释为一个数字,并将其转换为该数字所代表的字节。例如,一个128D的二进制字符串向量的格式为unbin('01010...01010')
。 - 使用
unhex()
函数产生一个二进制向量。这个函数将参数中的每一对十六进制数字解释为一个数字,并将其转换为该数字所代表的字节。例如,一个128D的二进制字符串向量的格式为unhex('FFEEDDCC...112233')
。
- 使用
distance()
函数应与 order by 和 limit 子句一起使用,以获取前几个候选结果。- 在 order by 子句中,distance 函数列的排序方向需要与向量索引的度量类型(Cosine、IP 等)相对应,否则将报错。当度量类型为
IP
时,排序方向必须为DESC
。
典型的 float32 向量数组搜索查询看起来像这样:
SELECT id, date, label,
distance(data, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3, 23, 4, 6, 26, 31, 121, 87, 40, 121, 82, 16, 12, 15, 41, 6, 10, 76, 48, 5, 3, 21, 42, 41, 50, 5, 17, 18, 64, 86, 54, 17, 6, 43, 62, 56, 84, 116, 108, 38, 26, 58, 63, 20, 87, 105, 37, 2, 2, 121, 121, 38, 25, 44, 33, 24, 46, 3, 16, 27, 74, 121, 55, 9, 4]) AS dist
FROM test_float_vector
ORDER BY dist LIMIT 10
此查询将返回test_float_vector
表中向量列与查询向量[3.0, 9, ..., 4]
之间的id
、date
、label
和距离。ORDER BY dist LIMIT 10
子句指定应返回前10个最接近的结果。
输出:
id | date | label | dist |
---|---|---|---|
3 | "2024-08-11" | "animal" | 0 |
790110 | "2001-10-14" | "person" | 102904 |
396372 | "1987-12-15" | "animal" | 108579 |
401952 | "1975-08-24" | "animal" | 117388 |
603558 | "1999-09-26" | "animal" | 118487 |
25589 | "1978-08-29" | "animal" | 119259 |
12632 | "2019-02-25" | "animal" | 119662 |
800289 | "2000-07-09" | "building" | 119673 |
16298 | "1997-03-11" | "animal" | 120011 |
395903 | "2020-08-19" | "animal" | 121352 |
查询结果将是一个包含三列的表:id
、date
、label
和dist
,分别显示向量的id、日期、标签和最接近的向量结果与查询向量之间的距离。
相应地,典型的二进制字符串向量搜索查询看起来像这样:
SELECT id, date, label,
distance(binary_data, unhex('ABCDEF0123456789ABCDEF0123456789')) AS dist
FROM test_binary_vector
ORDER BY dist LIMIT 10
# 带有过滤器的向量搜索
带有过滤器的向量搜索允许您根据其他列的值或距离值缩小结果范围。例如,以下查询将返回test_float_vector
表中向量列与查询向量[3.0, 9, ..., 4]
之间的id
、向量和距离,但仅限于id
列大于100000的行:
SELECT id, date, label,
distance(data, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3, 23, 4, 6, 26, 31, 121, 87, 40, 121, 82, 16, 12, 15, 41, 6, 10, 76, 48, 5, 3, 21, 42, 41, 50, 5, 17, 18, 64, 86, 54, 17, 6, 43, 62, 56, 84, 116, 108, 38, 26, 58, 63, 20, 87, 105, 37, 2, 2, 121, 121, 38, 25, 44, 33, 24, 46, 3, 16, 27, 74, 121, 55, 9, 4]) AS dist
FROM test_float_vector
WHERE id > 100000
ORDER BY dist LIMIT 10
输出:
id | date | label | dist |
---|---|---|---|
790110 | "2001-10-14" | "person" | 102904 |
396372 | "1987-12-15" | "animal" | 108579 |
401952 | "1975-08-24" | "animal" | 117388 |
603558 | "1999-09-26" | "animal" | 118487 |
800289 | "2000-07-09" | "building" | 119673 |
395903 | "2020-08-19" | "animal" | 121352 |
600737 | "1972-08-25" | "animal" | 125027 |
790101 | "1990-02-22" | "person" | 129224 |
790265 | "2019-05-26" | "building" | 133267 |
198290 | "1974-04-22" | "building" | 134178 |
要按distance
值进行过滤,请使用以下方式的WHERE
子句:
SELECT id, date, label,
distance(data, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3, 23, 4, 6, 26, 31, 121, 87, 40, 121, 82, 16, 12, 15, 41, 6, 10, 76, 48, 5, 3, 21, 42, 41, 50, 5, 17, 18, 64, 86, 54, 17, 6, 43, 62, 56, 84, 116, 108, 38, 26, 58, 63, 20, 87, 105, 37, 2, 2, 121, 121, 38, 25, 44, 33, 24, 46, 3, 16, 27, 74, 121, 55, 9, 4]) AS dist
FROM test_float_vector
WHERE dist < 110000
ORDER BY dist LIMIT 10
此查询将返回test_float_vector
表中向量列与查询向量[3.0, 9, ..., 4]
之间的id
、date
、label
和距离,但仅限于距离小于110000的行。
输出:
id | date | label | dist |
---|---|---|---|
3 | "2024-08-11" | "animal" | 0 |
790110 | "2001-10-14" | "person" | 102904 |
396372 | "1987-12-15" | "animal" | 108579 |
# 多列向量搜索
注意
该功能仅在 DB 版本 v1.8 或更高版本中可用。
多列向量搜索允许您在向量搜索查询中使用多个 distance()
函数。多个 distance()
函数的前几个候选项是基于 part 和行 ID 进行合并的。distance()
函数的结果类型为 Float32
。因此,对于具有两个向量 vec1 和 vec2 的行,只有一个向量出现在前几个候选项中,另一个向量的 distance()
值将为 NaN
。
典型的多列向量搜索查询如下所示:
SELECT id, date, label, distance(vec1, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3, 23, 4, 6, 26, 31, 121, 87, 40, 121, 82, 16, 12, 15, 41, 6, 10, 76, 48, 5, 3, 21, 42, 41, 50, 5, 17, 18, 64, 86, 54, 17, 6, 43, 62, 56, 84, 116, 108, 38, 26, 58, 63, 20, 87, 105, 37, 2, 2, 121, 121, 38, 25, 44, 33, 24, 46, 3, 16, 27, 74, 121, 55, 9, 4]) AS dist1,
distance(vec2, [3.0, 9, 45, 22, 28, 11, 4, 3, 77, 10, 4, 1, 1, 4, 3, 11, 23, 0, 0, 0, 26, 49, 6, 7, 5, 3, 3, 1, 11, 50, 8, 9, 11, 7, 15, 21, 12, 17, 21, 25, 121, 12, 4, 7, 4, 7, 4, 41, 28, 2, 0, 1, 10, 42, 22, 20, 1, 1, 4, 9, 31, 79, 16, 3]) AS dist2
FROM test_multiple_vectors
ORDER BY dist1 + dist2
LIMIT 10;
- 向量搜索查询应该结合使用 order by 和 limit 子句以获取前几个候选项。
- 对于
distance()
值包含NaN
的情况,您可以使用类似ifNotFinite(dist1, <default_value>)
的表达式为NaN
值提供适当的默认值。
# 向量索引配置选项的解释
向量索引有两种类型的参数:索引创建参数和搜索参数。
索引创建参数在索引创建时指定,例如
ALTER TABLE [db.]table_name
ADD VECTOR INDEX index_name column_name
[TYPE index_type
(
'creation_param1 = value1',
'creation_param2 = value2',
...
)]
在搜索过程中,可以指定搜索参数,例如:
SELECT
id,
distance('search_param1 = value1', 'search_param2 = value2')(column_name, query_vector) as dist
FROM [db.]table_name
ORDER BY dist LIMIT 10
# 通用索引创建参数
可以在创建任何类型的向量索引时使用以下参数:
metric_type
: 该参数确定了向量搜索中使用的距离度量。- 对于 float32 向量数组,有三个选项可用,默认值为
L2
。L2
: L2 度量,也称为欧几里得距离。Cosine
: 余弦距离,基于余弦相似度。余弦距离公式计算如下:
通过将距离值从 1 减去可以得到余弦相似度。IP
: 内积(IP)度量。请注意,在使用 IP 度量时,需要使用ORDER BY ... DESC
,因为更高的 IP 值表明相似度更大。
- 对于二进制字符串向量,有一个选项可用,默认值为
Hamming
。Hamming
: 两个等长向量之间的汉明距离是相应位置上不同符号的计数。Jaccard
: 杰卡德距离度量不相似度。杰卡德距离公式计算如下:
通过将距离值从 1 减去可以得到杰卡德系数。
- 对于 float32 向量数组,有三个选项可用,默认值为
# ScaNN
/ MSTG
/ BinaryMSTG
参数
ScaNN 是谷歌为高维向量空间开发的用于快速且可扩展的最近邻搜索的算法。
MyScale开发的多尺度树图(MSTG)算法是一种专有解决方案,旨在为标准和过滤向量搜索操作提供高数据密度和高性能。
索引创建参数:
ScaNN
、MSTG
和 BinaryMSTG
在索引创建过程中除了上述的 metric_type
外,不接受任何参数。
搜索参数:
alpha = float
: 此参数控制搜索操作的准确性。值越高,搜索越准确,但QPS越低。默认值为3,有效范围为1到4。
# FLAT
/ BinaryFLAT
参数
FLAT
和 BinaryFLAT
是向量索引的最简单形式,它们直接基于原始数据计算距离,不涉及任何额外的优化参数。这对于原型设计和确保搜索结果的准确性很有用,但由于其相对较慢的性能,不建议在生产环境中使用。
# IVFFLAT
参数
IVFFLAT
是一种分层索引,利用聚类将向量分成较小的簇,以实现更高效的搜索。
索引创建参数:
ncentroids = int
: 确定将所有向量数据划分为的簇的数量。较大的ncentroids
值会导致较慢的表构建时间。默认值为1024。
搜索参数:
nprobe = int
: 指定搜索操作期间要搜索的簇的数量。较大的值会导致较慢的搜索但更高的准确性。默认值为1。
建议的参数值:
建议为ncentroids
选择1000-10000之间的值,偏好接近数据量的平方根的值。如果ncentroids
太大,可能会影响性能。建议为nprobe
选择0.1-10%的ncentroids
值。
# IVFPQ
参数
索引创建参数:
ncentroids = int
: 参见IVFFLAT
。M = int
: 将原始向量维度减小为M
。M
必须能够整除原始向量维度。默认值为16。bit_size = int
: 指定用于替换原始向量的Product Quantization(PQ)编码表的大小。有效值是4的倍数,默认值为8。
搜索参数:
nprobe = int
: 参见IVFFLAT
。
建议的参数值:
ncentroids
和nprobe
的推荐值与IVFFLAT
相似。重要的是要注意,PQ的压缩比率计算公式为(bit_size * M) / (original_dimension * original_element_size)
。对于128维的float32向量,当M = 16
和bit_size = 8
时,对应的压缩比率为(16*8)/(128*32) = 1/32
。过高的压缩比率可能会严重影响搜索结果的准确性,因此建议将bit_size
保持在8,并将M
控制在原始维度的1/4以内,以避免此问题。
# IVFSQ
参数
索引创建参数:
ncentroids = int
: 参考IVFFlat
。bit_size = string
: 可接受的值为8bit
、6bit
、4bit
、8bit_uniform
、8bit_direct
、4bit_uniform
和QT_fp16
,默认值为8bit
。
标量量化(SQ)算法用于在保留维度数量的同时压缩每个向量维度。当 bit_size
设置为 8 时,压缩率约为 25%。算法的精度按照 8bit_direct
、8bit_uniform
和 8bit
的顺序递增,但索引构建速度与精度成反比。8bit_direct
使用 static_cast
将浮点数转换为 uint_8
,8bit_uniform
将所有浮点数均匀分成 256 个区间,8bit
将每个维度均匀分成 256 个区间。4bit_uniform
将数据分成 16 个区间进行量化。QT_fp16
是 SQ 算法的一种变体,使用半精度浮点数,详细信息可以在链接 https://gist.github.com/rygorous/2156668 (opens new window) 中找到。
搜索参数:
nprobe = int
: 参考IVFFlat
。
# HNSWFLAT
参数
分层可导航小世界(HNSW)算法是一种用于快速在高维空间中找到给定查询点的最近邻的近似最近邻搜索算法。它通过将数据点组织成多级图数据结构来实现这一目标。HNSW 算法利用“小世界”网络的原理,其中大多数节点之间只有很少的步骤,以导航图并高效地找到与查询点最接近的数据点。它以在大型数据集和高维空间中的高性能和可扩展性而闻名。
索引创建参数:
m = int
: 该参数确定 HNSW 图中每个数据点的邻居数量,并影响索引的质量。较大的m
值将导致搜索结果的准确性更高,但也会增加构建索引所需的时间。默认值为 16。ef_c = int
: 该参数确定在创建索引时 HNSW 使用的优先队列的大小,并影响索引的质量。较大的ef_c
值将导致搜索结果的精度更高,但也会增加构建索引所需的时间。默认值为 100。
搜索参数:
ef_s = int
: 该参数确定在搜索操作期间 HNSW 使用的优先队列的大小。较大的ef_s
值将导致搜索结果的精度更高,但也会增加搜索时间。默认值为 50。
建议的参数值:
通常建议将 m
设置在 8-128 之间,将 ef_c
设置在 50-400 之间。将 ef_c
值翻倍将大致使索引构建时间翻倍。ef_s
的值应根据搜索需求进行调整,建议使用与 ef_c
相同的值范围。如果需要低延迟要求,可以选择较低的值。
# HNSWSQ
参数
索引创建参数:
搜索参数:
ef_s = int
: 参考HNSWFLAT
。
建议的参数值:
参考 IVFSQ
获取 bit_size
的选择,其余参数参考 HNSWFLAT
。
# 性能调优建议
通常,我们建议使用 MSTG
/BinaryMSTG
索引和索引创建的默认值来优化性能。在搜索时,您可以根据所需的吞吐量和精度调整 alpha
值。
在 MyScale 中,数据会自动分成多个部分,并在内部为每个部分创建一个向量索引。为了获得最佳性能,我们建议在创建向量索引之前将表优化为一个数据部分。您可以使用以下命令来优化表:
OPTIMIZE TABLE test_vector FINAL
因此,操作的推荐顺序是:
- 使用
CREATE TABLE ...
创建表; - 使用
INSERT INTO ...
插入数据; - 优化表;
- 使用
ALTER TABLE ... ADD VECTOR INDEX
创建向量索引; - 等待向量索引构建完成;
- 开始搜索。
需要注意的是,在索引创建之后优化表可能会消耗大量内存。因此,请不要在索引创建之后优化表。