本页介绍了如何使用 Spanner 中的以下 ANN 距离函数查找近似最近邻 (ANN)、创建向量索引和查询向量嵌入:
APPROX_COSINE_DISTANCE
APPROX_EUCLIDEAN_DISTANCE
APPROX_DOT_PRODUCT
如果数据集较小,您可以使用 K 最近邻 (KNN) 来查找精确的 k 最近邻向量。不过,随着数据集的增大,KNN 搜索的延迟时间和费用也会增加。您可以使用 ANN 查找近似 k 最近邻,从而显著缩短延迟时间、降低成本。
近似 K 最近邻
在 ANN 搜索中,返回的 k 个向量并非真正的 Top-k 最近邻,因为 ANN 搜索会计算近似距离,并且可能不会查看数据集中的所有向量。有时,系统会返回一些并非前 k 个最近邻的向量。这称为“召回率损失”。您可以接受多少召回率损失取决于具体用例,但在大多数情况下,牺牲一点召回率来换取数据库性能的提升是可以接受的权衡。
如需详细了解 Spanner 近似距离函数,请参阅:
- GoogleSQL 中的
APPROX_COSINE_DISTANCE
- GoogleSQL 中的
APPROX_EUCLIDEAN_DISTANCE
- GoogleSQL 中的
APPROX_DOT_PRODUCT
向量索引
Spanner 使用专用向量索引来加快 ANN 向量搜索的速度。此索引利用了 Google 研究部门的可扩容最近邻 (ScaNN),这是一种高效的最近邻算法。
向量索引使用基于树的结构来划分数据,从而加快搜索速度。Spanner 提供双层和三层树配置:
- 二级树配置:叶节点 (
num_leaves
) 包含密切相关的向量组以及相应的形心。根级由所有叶节点的形心组成。 - 三级树配置:在概念上与二级树类似,但引入了一个额外的分支层 (
num_branches
),叶节点形心会从该分支层进一步分区,以形成根级别 (num_leaves
)。
Spanner 会为您选择一个索引。不过,如果您知道某个特定索引效果最佳,则可以使用 FORCE_INDEX
提示来选择使用最适合您的用例的向量索引。
如需了解详情,请参阅 VECTOR INDEX
语句。
限制
- 您无法预先拆分向量索引。如需了解详情,请参阅预拆分概览。
创建向量索引
为优化向量索引的召回率和性能,我们建议您:
在将大部分包含嵌入的行写入数据库后,创建向量索引。插入新数据后,您可能还需要定期重建向量索引。如需了解详情,请参阅重建向量索引。
使用
STORING
子句在向量索引中存储列的副本。如果列值存储在向量索引中,则 Spanner 会在索引的叶级执行过滤,以提升查询性能。如果列用于过滤条件,建议您存储该列。如需详细了解如何在索引中使用STORING
,请参阅为纯索引扫描创建索引。
创建表时,嵌入列必须是 FLOAT32
(推荐)或 FLOAT64
数据类型的数组,并且具有 vector_length 注解,用于指明向量的维度。
以下 DDL 语句会创建一个 Documents
表,其中包含一个向量长度为 DocEmbedding
的嵌入列:
CREATE TABLE Documents (
DocId INT64 NOT NULL,
...
DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);
填充 Documents
表后,您可以使用余弦距离在具有嵌入列 DocEmbedding
的 Documents
表上创建具有双层树和 1000 个叶节点的向量索引:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(DocEmbedding)
STORING (WordCount)
OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);
如需创建具有三级树和 100 万个叶节点的向量索引,请执行以下操作:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(NullableDocEmbedding)
STORING (WordCount)
WHERE NullableDocEmbedding IS NOT NULL
OPTIONS (distance_type = 'COSINE', tree_depth = 3, num_branches=1000, num_leaves = 1000000);
如果您的嵌入列未在表定义中标记为 NOT NULL
,您必须使用 WHERE COLUMN_NAME IS NOT NULL
子句声明该列:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(NullableDocEmbedding)
STORING (WordCount)
WHERE NullableDocEmbedding IS NOT NULL
OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);
过滤向量索引
您还可以创建过滤后的向量索引,以查找数据库中与过滤条件最匹配的相似项。过滤后的向量索引会选择性地为满足指定过滤条件的行编制索引,从而提高搜索性能。
在以下示例中,表 Documents
包含一个名为 Category
的列。在我们的矢量搜索中,我们希望为“技术”类别编制索引,因此我们创建了一个生成的列,如果未满足类别条件,该列的计算结果为 NULL。
CREATE TABLE Documents (
DocId INT64 NOT NULL,
Category STRING(MAX),
NullIfFiltered BOOL AS (IF(Category = 'Tech', TRUE, NULL)) HIDDEN,
DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);
然后,我们创建一个带有过滤器的向量索引。TechDocEmbeddingIndex
向量索引仅对“技术”类别中的文档编制索引。
CREATE VECTOR INDEX TechDocEmbeddingIndex
ON Documents(DocEmbedding)
STORING(NullIfFiltered)
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
OPTIONS (...);
当 Spanner 运行以下查询时,如果查询的过滤条件与 TechDocEmbeddingIndex
相匹配,系统会自动选择 TechDocEmbeddingIndex
并通过它来加速查询。该查询仅搜索“技术”类别中的文档。您还可以使用 {@FORCE_INDEX=TechDocEmbeddingIndex}
强制 Spanner 明确使用 TechDocEmbeddingIndex
。
SELECT *
FROM Documents
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
ORDER BY APPROX_(....)
LIMIT 10;
查询向量嵌入
如需查询向量索引,请使用以下三种近似距离函数之一:
APPROX_COSINE_DISTANCE
APPROX_EUCLIDEAN_DISTANCE
APPROX_DOT_PRODUCT
使用近似距离函数时,存在以下限制:
- 近似距离函数必须计算嵌入列与常量表达式(例如,参数或字面量)之间的距离。
- 近似距离函数输出必须在
ORDER BY
子句中用作唯一排序键,并且必须在ORDER BY
后指定LIMIT
。 - 查询必须明确过滤掉未编入索引的行。在大多数情况下,这意味着查询必须包含与向量索引定义匹配的
WHERE <column_name> IS NOT NULL
子句,除非该列已在表定义中标记为NOT NULL
。
如需查看详细的限制列表,请参阅近似距离函数参考页面。
示例
如需搜索与 [1.0, 2.0, 3.0]
最接近的 100 个向量,请执行以下操作:
SELECT DocId
FROM Documents
WHERE WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
ARRAY<FLOAT32>[1.0, 2.0, 3.0], DocEmbedding,
options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100
如果嵌入列可以为 null:
SELECT DocId
FROM Documents
WHERE NullableDocEmbedding IS NOT NULL AND WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
ARRAY<FLOAT32>[1.0, 2.0, 3.0], NullableDocEmbedding,
options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100
最佳做法
请遵循以下最佳实践,优化向量索引并改进查询结果。
调整向量搜索选项
最优的向量搜索值取决于使用情形、向量数据集和查询向量。您可能需要执行迭代调整,才能找到适合特定工作负载的最佳值。
以下是一些有助于您选择合适值的实用指南:
tree_depth
(树级别):如果被编入索引的表的行数少于 1, 000 万,请使用tree_depth
为2
。否则,tree_depth
为3
时,支持的表格最多可包含约 100 亿行。num_leaves
:使用数据集中的行数的平方根。值越大,向量索引构建时间就越长。请避免将num_leaves
设置为大于table_row_count/1000
,因为这会导致叶过小且性能不佳。num_leaves_to_search
:此选项用于指定要搜索的索引的叶节点数量。增加num_leaves_to_search
会提高召回率,但也会增加延迟时间和费用。我们建议将CREATE VECTOR INDEX
语句中定义的叶总数的 1% 作为num_leaves_to_search
的值。如果您使用的是过滤条件子句,请增加此值以扩大搜索范围。
如果召回率达到可接受的水平,但查询费用过高,导致最大 QPS 较低,请尝试按照以下步骤增加 num_leaves
:
- 将
num_leaves
设置为其原始值的某个倍数 k(例如2 * sqrt(table_row_count)
)。 - 将
num_leaves_to_search
设置为其原始值的相同倍数 k。 - 尝试减少
num_leaves_to_search
,以在保持召回率的同时提高成本和 QPS。
提高回想度
召回率变得更糟的原因有很多,包括:
num_leaves_to_search
太小:您可能会发现,要为某些查询向量查找最近邻更具挑战性,因此增加num_leaves_to_search
以搜索更多叶可以帮助提高召回率。最近的查询可能已发生变化,包含更多此类具有挑战性的向量。向量索引需要重建:向量索引的树结构在创建时会针对数据集进行优化,之后会变为静态。因此,如果在创建初始向量索引后添加了显著不同的向量,则树结构可能不太理想,从而导致召回率不佳。
重建向量索引
如需在不中断服务的情况下重建向量索引,请执行以下操作:
- 在与当前向量索引相同的嵌入列上创建新的向量索引,并根据需要更新参数(例如
OPTIONS
)。 - 索引创建完成后,使用
FORCE_INDEX
提示指向新索引,以更新向量搜索查询。这样可确保查询使用新的向量索引。您可能还需要在新查询中重新调整num_leaves_to_search
。 - 删除过时的向量索引。
后续步骤
详细了解 GoogleSQL
APPROXIMATE_COSINE_DISTANCE()
、APPROXIMATE_EUCLIDEAN_DISTANCE()
、APPROXIMATE_DOT_PRODUCT()
函数。如需查看使用 ANN 的逐步示例,请尝试阅读 Spanner 向量搜索使用入门。