> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-trino-dialect.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> 精确与近似向量搜索文档

# 精确与近似向量搜索

export const ExperimentalBadge = () => {
  return <a href="https://clickhouse.com/docs/reference/settings/beta-and-experimental-features#experimental-features" className="experimentalBadge">
            <div className="experimentalIcon">
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path strokeWidth="1.25" d="M5.5 2H10.5" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
                <path strokeWidth="1.25" d="M9.50015 2V6.19625L13.4283 12.7425C13.4738 12.8183 13.4985 12.9049 13.4996 12.9934C13.5008 13.0818 13.4785 13.169 13.435 13.246C13.3914 13.323 13.3283 13.3871 13.2519 13.4317C13.1755 13.4764 13.0886 13.4999 13.0002 13.5H3.00015C2.91164 13.5 2.8247 13.4766 2.74822 13.432C2.67174 13.3874 2.60847 13.3233 2.56487 13.2463C2.52126 13.1693 2.49889 13.082 2.50004 12.9935C2.50119 12.905 2.52582 12.8184 2.5714 12.7425L6.50015 6.19625V2" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
                <path strokeWidth="1.25" d="M4.47656 9.56754C5.30344 9.41254 6.47656 9.47942 7.99969 10.25C10.0153 11.2707 11.4216 11.0569 12.2184 10.7282" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
        </div>
            Experimental 功能
        </a>;
};

对于给定点，在多维 (向量) 空间中查找距离其最近的 N 个点，这个问题称为[最近邻搜索](https://en.wikipedia.org/wiki/Nearest_neighbor_search)，简称向量搜索。
解决向量搜索通常有两种通用方法：

* 精确向量搜索会计算给定点与向量空间中所有点之间的距离。这可以确保达到尽可能高的准确性，也就是说，返回的点一定是真正的最近邻。由于需要穷尽整个向量空间，精确向量搜索在实际应用中可能会过慢。
* 近似向量搜索是指一类技术 (例如图、随机森林等特殊数据结构) ，其计算速度远快于精确向量搜索。其结果精度通常已经“足够好”，能够满足实际使用需求。许多近似技术都提供参数，用于在结果精度和搜索时间之间进行权衡。

向量搜索 (精确或近似) 可以用如下 SQL 形式来编写：

```sql theme={null}
WITH [...] AS reference_vector
SELECT [...]
FROM table
WHERE [...] -- a WHERE clause is optional
ORDER BY <DistanceFunction>(vectors, reference_vector)
LIMIT <N>
```

向量空间中的点存储在名为 `vectors` 的 [Array(Float64)](/zh/reference/data-types/array)、[Array(Float32)](/zh/reference/data-types/array) 或 [Array(BFloat16)](/zh/reference/data-types/array) 类型列中。
在实际使用中，我们通常建议使用 [BFloat16](/zh/reference/data-types/float) 数组。
参考向量是一个常量数组，通过公用表表达式给出。
`<DistanceFunction>` 用于计算参考点与所有已存储点之间的距离。
这里可以使用任意一种可用的[距离函数](/zh/reference/functions/regular-functions/distance-functions)。
`<N>` 指定要返回多少个邻居。

<div id="exact-nearest-neighbor-search">
  ## 精确向量搜索
</div>

可以直接使用上述 SELECT 查询按原样执行精确向量搜索。
这类查询的运行时间通常与已存储向量的数量、向量元素数量 (= 维度) 以及向量元素的位宽成正比。
此外，由于 ClickHouse 会对所有向量执行穷举扫描，运行时间还取决于该查询使用的线程数 (请参见设置 [max\_threads](/zh/reference/settings/session-settings/max-threads#max_threads)) 。

<div id="exact-nearest-neighbor-search-example">
  ### 示例
</div>

```sql theme={null}
CREATE TABLE tab
(
    id Int32,
    vec Array(BFloat16)
)
ENGINE = MergeTree
ORDER BY id;

INSERT INTO tab VALUES (0, [1.0, 0.0]), (1, [1.1, 0.0]), (2, [1.2, 0.0]), (3, [1.3, 0.0]), (4, [1.4, 0.0]), (5, [1.5, 0.0]), (6, [0.0, 2.0]), (7, [0.0, 2.1]), (8, [0.0, 2.2]), (9, [0.0, 2.3]), (10, [0.0, 2.4]), (11, [0.0, 2.5]);

WITH [0., 2.] AS reference_vec
SELECT id, vec
FROM tab
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 3;
```

返回值

```result theme={null}
   ┌─id─┬─vec─────┐
1. │  6 │ [0,2]   │
2. │  7 │ [0,2.1] │
3. │  8 │ [0,2.2] │
   └────┴─────────┘
```

<div id="approximate-nearest-neighbor-search">
  ## 近似向量搜索
</div>

<div id="vector-similarity-index">
  ### 向量相似度索引
</div>

ClickHouse 提供了一种特殊的“向量相似度”索引，用于执行近似向量搜索。

<Note>
  向量相似度索引适用于 ClickHouse 25.8 及更高版本。
  如果遇到问题，欢迎在 [ClickHouse 仓库](https://github.com/clickhouse/clickhouse/issues)中提交 issue。
</Note>

<div id="creating-a-vector-similarity-index">
  #### 创建向量相似度索引
</div>

可以像下面这样在新表上创建向量相似度索引：

```sql theme={null}
CREATE TABLE table
(
    [...],
    vectors Array([Float64|Float32|BFloat16]),
    INDEX <index_name> vectors TYPE vector_similarity(<type>, <distance_function>, <dimensions>) [GRANULARITY <N>]
)
ENGINE = MergeTree
ORDER BY [...]
```

或者，如需为现有表添加向量相似度索引：

```sql theme={null}
ALTER TABLE table ADD INDEX <index_name> vectors TYPE vector_similarity(<type>, <distance_function>, <dimensions>) [GRANULARITY <N>];
```

向量相似度索引是一类特殊的跳过索引 (参见[此处](/zh/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-data_skipping-indexes)和[此处](/zh/concepts/features/performance/skip-indexes/skipping-indexes)) 。
因此，上面的 `ALTER TABLE` 语句只会为今后新插入表中的数据构建索引。
若要同时为现有数据构建索引，则需要将其物化：

```sql theme={null}
ALTER TABLE table MATERIALIZE INDEX <index_name> SETTINGS mutations_sync = 2;
```

函数 `<distance_function>` 必须是

* `L2Distance`，即 [Euclidean distance](https://en.wikipedia.org/wiki/Euclidean_distance) (欧几里得距离) ，表示欧几里得空间中两点之间的直线距离，
* `cosineDistance`，即 [cosine distance](https://en.wikipedia.org/wiki/Cosine_similarity#Cosine_distance) (余弦距离) ，表示两个非零向量之间的夹角，或
* `dotProduct`，即 [dot product](https://en.wikipedia.org/wiki/Dot_product) (内积) ，表示两个向量对应元素乘积之和。在归一化数据上，它等价于 `cosineDistance`。

对于归一化数据，通常 `L2Distance` 是最佳选择；否则，建议使用 `cosineDistance` 来补偿标度差异。

<Note>
  对于距离函数 `L2Distance` 和 `cosineDistance`，值越小表示相似度越高；而对于 `dotProduct`，值越大表示相似度越高。
  因此，使用 `L2Distance` 和 `cosineDistance` 的向量索引只能用于 `SELECT [...] ORDER BY [...] ASC` 查询 (`ASC` 是 `ORDER BY` 的默认值) ，而为 `dotProduct` 构建的向量索引只能用于 `SELECT [...] ORDER BY [...] DESC` 查询。
</Note>

`<dimensions>` 指定底层列中数组的元素个数。
如果 ClickHouse 在创建索引时发现数组的元素个数不一致，则会放弃该索引并返回错误。

可选的 GRANULARITY 参数 `<N>` 表示索引粒度的大小 (见[此处](/zh/concepts/features/performance/skip-indexes/skipping-indexes)) 。
与默认索引粒度为 1 的常规跳过索引不同，向量相似度索引默认使用 1 亿作为索引粒度。
这个值可确保即使对于较大的 parts，内部构建的索引数量也很少。
我们建议仅由理解其影响的高级用户修改索引粒度 (见[下文](#differences-to-regular-skipping-indexes)) 。

向量相似度索引是通用的，也就是说它们可以支持不同的近似搜索方法。
实际使用的方法由参数 `<type>` 指定。
截至目前，唯一可用的方法是 HNSW ([学术论文](https://arxiv.org/abs/1603.09320)) ，这是一种流行且先进的近似向量搜索技术，基于分层近邻图。
如果使用 HNSW 作为 `<type>`，用户还可以选择指定更多 HNSW 专用参数：

```sql theme={null}
CREATE TABLE table
(
    [...],
    vectors Array([Float64|Float32|BFloat16]),
    INDEX index_name vectors TYPE vector_similarity('hnsw', <distance_function>, <dimensions>[, <quantization>, <hnsw_max_connections_per_layer>, <hnsw_candidate_list_size_for_construction>]) [GRANULARITY N]
)
ENGINE = MergeTree
ORDER BY [...]
```

可用的 HNSW 专用参数如下：

* `<quantization>` 用于控制邻近图中向量的量化方式。可能值为 `f64`、`f32`、`f16`、`bf16`、`i8` 或 `b1`。默认值为 `bf16`。请注意，此参数不会影响底层列中向量的表示形式。
* `<hnsw_max_connections_per_layer>` 用于控制每个图节点的邻居数，也称为 HNSW 超参数 `M`。默认值为 `32`。值为 `0` 表示使用默认值。
* `<hnsw_candidate_list_size_for_construction>` 用于控制构建 HNSW 图期间动态候选列表的大小，也称为 HNSW 超参数 `ef_construction`。默认值为 `128`。值为 `0` 表示使用默认值。

所有 HNSW 专用参数的默认值在大多数使用场景下都表现良好。
因此，我们不建议自定义这些 HNSW 专用参数。

此外，还有以下限制：

* 向量相似度索引只能构建在类型为 [Array(Float32)](/zh/reference/data-types/array)、[Array(Float64)](/zh/reference/data-types/array) 或 [Array(BFloat16)](/zh/reference/data-types/array) 的列上。不允许使用可空浮点数组和低基数浮点数组，例如 `Array(Nullable(Float32))` 和 `Array(LowCardinality(Float32))`。
* 向量相似度索引必须构建在单个列上。
* 向量相似度索引可以构建在计算表达式上 (例如 `INDEX index_name arraySort(vectors) TYPE vector_similarity([...])`) ，但这类索引之后无法用于近似邻居搜索。
* 向量相似度索引要求底层列中的所有数组都必须包含 `<dimension>` 个元素——这一点会在创建索引时进行检查。为了尽早发现不满足此要求的情况，用户可以为向量列添加一个[约束](/zh/reference/statements/create/table#constraints)，例如 `CONSTRAINT same_length CHECK length(vectors) = 256`。
* 同样，底层列中的数组值不能为空 (`[]`) ，也不能为默认值 (默认值同样是 `[]`) 。

**估算存储和内存消耗**

为典型 AI 模型 (例如大语言模型 [LLM](https://en.wikipedia.org/wiki/Large_language_model)) 生成的向量，通常由数百个或数千个浮点值组成。
因此，单个向量值可能会占用数 KB 的内存。
如果用户想估算表中底层向量列所需的存储空间，以及向量相似度索引所需的主内存，可以使用下面两个公式：

表中向量列的存储消耗 (未压缩) ：

```text theme={null}
Storage consumption = Number of vectors * Dimension * Size of column data type
```

以 [dbpedia dataset](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M) 为例：

```text theme={null}
存储消耗 = 100万 * 1536 * 4（Float32）= 6.1 GB
```

必须先将向量相似度索引从磁盘完整加载到主内存中，才能执行搜索。
同样，向量索引也是先在内存中完整构建，然后再保存到磁盘。

加载向量索引所需的内存占用：

```text theme={null}
索引中向量的内存占用 (mv) = 向量数量 * 维度 * 量化数据类型的大小
内存图的内存占用 (mg) = 向量数量 * hnsw_max_connections_per_layer * Bytes_per_node_id (= 4) * Layer_node_repetition_factor (= 2)

内存消耗：mv + mg
```

[dbpedia 数据集](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M)示例：

```text theme={null}
Memory for vectors in the index (mv) = 1 million * 1536 * 2 (for BFloat16) = 3072 MB
Memory for in-memory graph (mg) = 1 million * 64 * 2 * 4 = 512 MB

Memory consumption = 3072 + 512 = 3584 MB
```

上述公式未将向量相似度索引用于分配预分配缓冲区、缓存等运行时数据结构所需的额外内存计算在内。

<div id="using-a-vector-similarity-index">
  #### 使用向量相似度索引
</div>

<Note>
  要使用向量相似度索引，[compatibility](/zh/reference/settings/session-settings/compatibility#compatibility) 设置必须为 `''` (默认值) ，或者为 `'25.1'` 或更高版本。
</Note>

向量相似度索引支持以下形式的 SELECT 查询：

```sql theme={null}
WITH [...] AS reference_vector
SELECT [...]
FROM table
WHERE [...] -- a WHERE clause is optional
ORDER BY <DistanceFunction>(vectors, reference_vector)
LIMIT <N>
```

ClickHouse 的查询优化器会尝试匹配上述查询模板，并利用可用的向量相似度索引。
只有当 SELECT 查询中的距离函数与索引定义中的距离函数一致时，查询才能使用向量相似度索引。

高级用户可以为设置项 [hnsw\_candidate\_list\_size\_for\_search](/zh/reference/settings/session-settings/other#hnsw_candidate_list_size_for_search) (也称为 HNSW 超参数 "ef\_search") 指定自定义值，以调整搜索过程中候选列表的大小 (例如 `SELECT [...] SETTINGS hnsw_candidate_list_size_for_search = <value>`) 。
该设置项的默认值 256 在绝大多数场景下均能满足需求。
设置值越高，精度越好，但性能开销也越大。

如果查询可以使用向量相似度索引，ClickHouse 会检查 SELECT 查询中提供的 LIMIT `<N>` 是否在合理范围内。
更具体地说，如果 `<N>` 大于设置项 [max\_limit\_for\_vector\_search\_queries](/zh/reference/settings/session-settings/max#max_limit_for_vector_search_queries) 的值 (默认值为 100) ，则会返回错误。
过大的 LIMIT 值会降低搜索速度，通常意味着存在使用方式上的错误。

要检查 SELECT 查询是否使用了向量相似度索引，可以在查询前添加 `EXPLAIN indexes = 1`。

以下示例中，查询

```sql theme={null}
EXPLAIN indexes = 1
WITH [0.462, 0.084, ..., -0.110] AS reference_vec
SELECT id, vec
FROM tab
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 10;
```

可能返回

```result theme={null}
    ┌─explain─────────────────────────────────────────────────────────────────────────────────────────┐
 1. │ Expression (Project names)                                                                      │
 2. │   Limit (preliminary LIMIT (without OFFSET))                                                    │
 3. │     Sorting (Sorting for ORDER BY)                                                              │
 4. │       Expression ((Before ORDER BY + (Projection + Change column names to column identifiers))) │
 5. │         ReadFromMergeTree (default.tab)                                                         │
 6. │         Indexes:                                                                                │
 7. │           PrimaryKey                                                                            │
 8. │             Condition: true                                                                     │
 9. │             Parts: 1/1                                                                          │
10. │             Granules: 575/575                                                                   │
11. │           Skip                                                                                  │
12. │             Name: idx                                                                           │
13. │             Description: vector_similarity GRANULARITY 100000000                                │
14. │             Parts: 1/1                                                                          │
15. │             Granules: 10/575                                                                    │
    └─────────────────────────────────────────────────────────────────────────────────────────────────┘
```

在此示例中，[dbpedia 数据集](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M) 中的 100 万个向量 (每个向量维度为 1536) 存储在 575 个粒度中，即每个粒度约 1.7k 行。该查询请求 10 个最近邻，向量相似度索引在 10 个独立粒度中找到这 10 个最近邻。查询执行期间将读取这 10 个粒度。

如果输出中包含 `Skip` 以及向量索引的名称和类型 (在本示例中为 `idx` 和 `vector_similarity`) ，则表示向量相似度索引已生效。
在本例中，向量相似度索引跳过了四个粒度中的两个，即 50% 的数据。
可跳过的粒度越多，索引的使用效率就越高。

<Tip>
  要强制使用索引，你可以在运行 SELECT 查询时启用设置 [force\_data\_skipping\_indices](/zh/reference/settings/session-settings/force#force_data_skipping_indices) (将索引名称作为该设置的值) 。
</Tip>

**后过滤与前过滤**

用户可以选择在 SELECT 查询中通过 `WHERE` 子句指定额外的过滤条件。
ClickHouse 将采用后过滤或前过滤策略对这些过滤条件进行求值。
简而言之，两种策略的区别在于过滤条件的求值顺序：

* 后过滤意味着会先评估向量相似度索引，随后 ClickHouse 再评估 `WHERE` 子句中指定的附加过滤条件。
* 前过滤意味着过滤条件的评估顺序正好相反。

这两种策略各有不同的权衡：

* 后过滤的一个普遍问题是，返回的行数可能少于 `LIMIT <N>` 子句请求的数量。当向量相似度索引返回的一行或多行结果不满足附加过滤条件时，就会出现这种情况。
* 前过滤通常仍是一个尚未解决的问题。某些专门的向量数据库提供了前过滤算法，但大多数关系型数据库 (包括 ClickHouse) 都会退回到精确近邻搜索，也就是不使用索引的穷举扫描。

采用哪种策略取决于过滤条件。

*附加过滤条件是分区键的一部分*

如果附加过滤条件是分区键的一部分，ClickHouse 就会应用分区剪枝。
例如，某个表按列 `year` 进行范围分区，并执行以下查询：

```sql theme={null}
WITH [0., 2.] AS reference_vec
SELECT id, vec
FROM tab
WHERE year = 2025
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 3;
```

ClickHouse 会剪枝掉除 2025 年分区之外的所有分区。

*无法通过索引评估额外过滤条件*

如果额外过滤条件无法通过索引 (主键索引、跳过索引) 评估，ClickHouse 将应用后过滤。

*可以使用主键索引评估额外过滤条件*

如果额外过滤条件可以通过[主键](/zh/reference/engines/table-engines/mergetree-family/mergetree#primary-key)评估 (即它们构成主键的前缀) ，并且

* 如果过滤条件在某个分片内至少排除一行，ClickHouse 将回退为对该分片内“保留下来”的范围执行前过滤，
* 如果过滤条件在某个分片内没有排除任何行，ClickHouse 将对该分片执行后过滤。

在实际场景中，后一种情况通常不太可能发生。

*可以使用跳过索引评估额外过滤条件*

如果额外过滤条件可以通过[跳过索引](/zh/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-data_skipping-indexes) (minmax 索引、set 索引等) 评估，ClickHouse 会执行后过滤。
在这种情况下，会先评估向量相似度索引，因为预计与其他跳过索引相比，它能过滤掉更多行。

若要更细致地控制后过滤与前过滤，可以使用两个设置：

设置 [vector\_search\_filter\_strategy](/zh/reference/settings/session-settings/vector-search#vector_search_filter_strategy) (默认值：`auto`，即实现上述启发式策略) 可以设为 `prefilter`。
这对于额外过滤条件选择性极高的场景很有用，可用于强制启用前过滤。
例如，以下查询可能会从前过滤中受益：

```sql theme={null}
SELECT bookid, author, title
FROM books
WHERE price < 2.00
ORDER BY cosineDistance(book_vector, getEmbedding('Books on ancient Asian empires'))
LIMIT 10
```

假设价格低于 2 美元的图书极少，后过滤可能会返回零行，因为向量索引返回的前 10 个匹配项价格都可能高于 2 美元。
通过强制使用前过滤 (在查询中添加 `SETTINGS vector_search_filter_strategy = 'prefilter'`) ，ClickHouse 会先找出所有价格低于 2 美元的图书，然后对这些图书执行穷举向量搜索。

作为解决上述问题的另一种方法，可以将 [vector\_search\_index\_fetch\_multiplier](/zh/reference/settings/session-settings/vector-search#vector_search_index_fetch_multiplier) (默认值：`1.0`，最大值：`1000.0`) 配置为大于 `1.0` 的值 (例如 `2.0`) 。
从向量索引中拉取的最近邻数量会乘以该设置值，然后再对这些行应用额外的过滤条件，以返回 LIMIT 指定数量的行。
例如，我们可以再次发起查询，但将 multiplier 设置为 `3.0`：

```sql theme={null}
SELECT bookid, author, title
FROM books
WHERE price < 2.00
ORDER BY cosineDistance(book_vector, getEmbedding('Books on ancient Asian empires'))
LIMIT 10
SETTING vector_search_index_fetch_multiplier = 3.0;
```

ClickHouse 将从每个分片中的向量索引拉取 3.0 x 10 = 30 个最近邻，然后再评估额外的过滤器。
最终只会返回距离最近的十个近邻。
需要注意的是，设置 `vector_search_index_fetch_multiplier` 可以缓解这个问题，但在极端情况下 (WHERE 条件选择性非常强) ，返回的行数仍可能少于请求的 N 行。

**重评分**

ClickHouse 中的跳过索引通常在粒度级别进行过滤。也就是说， (在内部) 查询一次跳过索引会返回一个可能匹配的粒度列表，从而减少后续扫描中需要读取的数据量。
这对一般的跳过索引来说效果很好，但对于向量相似度索引，则会造成“粒度不匹配”。
更具体地说，向量相似度索引会针对给定的参考向量找出最相似的 N 个向量的行号。
在设置 `vector_search_with_rescoring = 1` 时，ClickHouse 会读取候选行中原始的全精度向量，并在常规 SQL 管道中计算最终距离。
当查询计划允许时，ClickHouse 会在最终距离计算之前，将扫描结果过滤为向量索引返回的候选行。
这一步称为重评分，它可以提高准确性，尤其是在量化向量索引中，因为最终排名使用的是存储的向量，而不是索引距离。
如果附加过滤器移除了过多候选项，或者需要更高的召回率，请增大设置 `vector_search_index_fetch_multiplier`，以便向量索引返回更多候选行用于重评分。

因此，ClickHouse 提供了一种优化，可禁用重评分，并直接从索引返回最相似的向量及其距离。
该优化默认启用，参见设置 [vector\_search\_with\_rescoring](/zh/reference/settings/session-settings/vector-search#vector_search_with_rescoring)。
其大致工作方式是，ClickHouse 将最相似的向量及其距离通过虚拟列 `_distance` 提供出来。
要查看这一点，请使用 `EXPLAIN header = 1` 运行向量搜索查询：

```sql theme={null}
EXPLAIN header = 1
WITH [0., 2.] AS reference_vec
SELECT id
FROM tab
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 3
SETTINGS vector_search_with_rescoring = 0
```

```result theme={null}
Query id: a2a9d0c8-a525-45c1-96ca-c5a11fa66f47

    ┌─explain────────────────────────────────────────────────────────────────────────────────────────────────────┐
 1. │ Expression (Project names)                                                                                 │
 2. │ Header: id Int32                                                                                           │
 3. │   Limit (preliminary LIMIT (without OFFSET))                                                               │
 4. │   Header: L2Distance(__table1.vec, _CAST([0., 2.]_Array(BFloat16), 'Array(BFloat16)'_String)) BFloat16     │
 5. │           __table1.id Int32                                                                                │
 6. │     Sorting (Sorting for ORDER BY)                                                                         │
 7. │     Header: L2Distance(__table1.vec, _CAST([0., 2.]_Array(BFloat16), 'Array(BFloat16)'_String)) BFloat16   │
 8. │             __table1.id Int32                                                                              │
 9. │       Expression ((Before ORDER BY + (Projection + Change column names to column identifiers)))            │
10. │       Header: L2Distance(__table1.vec, _CAST([0., 2.]_Array(BFloat16), 'Array(BFloat16)'_String)) BFloat16 │
11. │               __table1.id Int32                                                                            │
12. │         ReadFromMergeTree (default.tab)                                                                    │
13. │         Header: id Int32                                                                                   │
14. │                 _distance BFloat16                                                                         │
    └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```

<Note>
  在未启用重评分 (`vector_search_with_rescoring = 0`) 且启用并行副本的情况下运行的查询，可能仍会回退到重评分。
</Note>

<div id="performance-tuning">
  #### 性能调优
</div>

**压缩调优**

在几乎所有使用场景中，底层列中的向量都是稠密的，因此通常难以有效压缩。
因此，[压缩](/zh/reference/statements/create/table/codec)会降低对向量列执行插入和读取的性能。
因此，我们建议禁用压缩。
为此，请像下面这样为向量列指定 `CODEC(NONE)`：

```sql theme={null}
CREATE TABLE tab
(
    id Int32,
    vec Array(BFloat16) CODEC(NONE),
    INDEX idx vec TYPE vector_similarity('hnsw', 'L2Distance', 2)
)
ENGINE = MergeTree ORDER BY id;
```

**调优索引创建**

向量相似度索引的生命周期与 parts 的生命周期密切相关。
换句话说，每当创建一个定义了向量相似度索引的新 part 时，对应的索引也会同时创建。
这通常发生在数据[插入](/zh/concepts/features/operations/insert/inserting-data)时，或在[合并](/zh/concepts/core-concepts/merges)过程中。
遗憾的是，HNSW 的索引创建耗时较长是众所周知的问题，这会显著拖慢插入和合并操作。
因此，向量相似度索引更适合用于数据不可变或很少发生变化的场景。

要加快索引创建，可以采用以下方法：

首先，可以将索引创建并行化。
可通过服务器设置 [max\_build\_vector\_similarity\_index\_thread\_pool\_size](/zh/reference/settings/server-settings/settings/max#max_build_vector_similarity_index_thread_pool_size) 配置用于创建索引的最大线程数。
为获得最佳性能，建议将该设置值配置为 CPU 核心数。

其次，为了加快 INSERT 语句的执行，用户可以通过会话设置 [materialize\_skip\_indexes\_on\_insert](/zh/reference/settings/session-settings/materialize#materialize_skip_indexes_on_insert) 禁止在新插入的 parts 上创建跳过索引。
对此类 parts 执行的 SELECT 查询将回退为精确搜索。
由于新插入的 parts 相对于整个表通常较小，因此预计对性能的影响可以忽略不计。

第三，为了加快合并，用户可以通过会话设置 [materialize\_skip\_indexes\_on\_merge](/zh/reference/settings/merge-tree-settings/materialize#materialize_skip_indexes_on_merge) 禁止在已合并的 parts 上创建跳过索引。
配合语句 [ALTER TABLE \[...\] MATERIALIZE INDEX \[...\]](/zh/reference/statements/alter/skipping-index#materialize-index)，这可以显式控制向量相似度索引的生命周期。
例如，可以将索引创建延后到所有数据都已摄取完成之后，或延后到系统负载较低的时段 (如周末) 再进行。

**调优索引使用**

SELECT 查询要使用向量相似度索引，需要先将其加载到主内存中。
为避免重复将同一个向量相似度索引加载到主内存，ClickHouse 为这类索引提供了专用的内存缓存。
缓存越大，不必要的加载就越少。
可通过服务器设置 [vector\_similarity\_index\_cache\_size](/zh/reference/settings/server-settings/settings/vector-similarity#vector_similarity_index_cache_size) 配置缓存的最大大小。
默认情况下，缓存最大可增长到 5 GB。

以下日志消息 (`system.text_log`) 表明正在加载向量相似度索引。
如果这类消息在不同的向量搜索查询中反复出现，则说明缓存容量过小。

```text theme={null}
2026-02-03 07:39:10.351635 [1386] f0ac5c85-1b1c-4f35-8848-87a1d1aa00ba : VectorSimilarityIndex Start loading vector similarity index

<...>

2026-02-03 07:40:25.217603 [1386] f0ac5c85-1b1c-4f35-8848-87a1d1aa00ba : VectorSimilarityIndex Loaded vector similarity index: max_level = 2, connectivity = 64, size = 1808111, capacity = 1808111, memory_usage = 8.00 GiB, bytes_per_vector = 4096, scalar_words = 1024, nodes = 1808111, edges = 51356964, max_edges = 233395072
```

<Note>
  向量相似度索引缓存用于存储向量索引粒度。
  如果单个向量索引粒度大于缓存容量，则不会被缓存。
  因此，请务必计算向量索引的大小 (根据“估算存储和内存消耗”中的公式，或参考 [system.data\_skipping\_indices](/zh/reference/system-tables/data_skipping_indices)) ，并据此设置相应的缓存容量。
</Note>

*我们再次强调，在排查向量搜索查询缓慢的问题时，首先应检查向量索引缓存，并在必要时增大其容量。*

当前向量相似度索引缓存的大小可在 [system.metrics](/zh/reference/system-tables/metrics) 中查看：

```sql theme={null}
SELECT metric, value
FROM system.metrics
WHERE metric = 'VectorSimilarityIndexCacheBytes'
```

可从 [system.query\_log](/zh/reference/system-tables/query_log) 获取具有特定查询 id 的查询的缓存命中和未命中信息：

```sql theme={null}
SYSTEM FLUSH LOGS query_log;

SELECT ProfileEvents['VectorSimilarityIndexCacheHits'], ProfileEvents['VectorSimilarityIndexCacheMisses']
FROM system.query_log
WHERE type = 'QueryFinish' AND query_id = '<...>'
ORDER BY event_time_microseconds;
```

对于生产环境中的用例，我们建议将缓存设置得足够大，以确保所有向量索引始终驻留在内存中。

**调整量化**

[量化](https://huggingface.co/blog/embedding-quantization)是一种用于降低向量内存占用，以及减少构建和遍历向量索引计算开销的技术。
ClickHouse 向量索引支持以下量化选项：

| Quantization   | Name              | Storage per dimension |
| -------------- | ----------------- | --------------------- |
| f32            | 单精度               | 4 字节                  |
| f16            | 半精度               | 2 字节                  |
| bf16 (default) | 半精度 (brain float) | 2 字节                  |
| i8             | 四分之一精度            | 1 字节                  |
| b1             | 二进制               | 1 比特                  |

与搜索原始全精度浮点值 (`f32`) 相比，量化会降低向量搜索的精度。
不过，在大多数数据集上，半精度 brain float 量化 (`bf16`) 带来的精度损失微乎其微，因此向量相似度索引默认采用这种量化方式。
四分之一精度 (`i8`) 和二进制 (`b1`) 量化会给向量搜索带来较明显的精度损失。
只有在向量相似度索引的大小显著超过可用 DRAM 容量时，我们才建议使用这两种量化方式。
在这种情况下，我们还建议启用重评分 ([vector\_search\_index\_fetch\_multiplier](/zh/reference/settings/session-settings/vector-search#vector_search_index_fetch_multiplier)、[vector\_search\_with\_rescoring](/zh/reference/settings/session-settings/vector-search#vector_search_with_rescoring)) 以提高准确性。
只有在以下情况下才建议使用二进制量化：1) 嵌入向量已归一化 (即向量长度 = 1，OpenAI 模型通常是归一化的)；2) 距离函数使用的是余弦距离。
二进制量化在内部使用 Hamming 距离来构建和搜索邻近图。
重评分步骤会使用表中存储的原始全精度向量，通过余弦距离识别最近邻。

**调整数据传输**

向量搜索查询中的参考向量由用户提供，通常通过调用大语言模型 (LLM) 获取。
在 ClickHouse 中执行向量搜索的典型 Python 代码可能如下所示

```python theme={null}
search_v = openai_client.embeddings.create(input = "[Good Books]", model='text-embedding-3-large', dimensions=1536).data[0].embedding

params = {'search_v': search_v}
result = chclient.query(
   "SELECT id FROM items
    ORDER BY cosineDistance(vector, %(search_v)s)
    LIMIT 10",
    parameters = params)
```

嵌入向量 (上述代码片段中的 `search_v`) 的维度可能非常大。
例如，OpenAI 提供的模型会生成维度为 1536 甚至 3072 的嵌入向量。
在上述代码中，ClickHouse Python driver 会将嵌入向量替换为便于人类阅读的字符串，随后再将整个 SELECT 查询作为字符串发送。
假设嵌入向量由 1536 个单精度浮点数组成，发送的字符串长度可达 20 kB。
这会导致 CPU 大量消耗在标记化、解析以及成千上万次字符串到浮点数的转换上。
此外，ClickHouse server 日志文件也需要占用大量空间，同时还会导致 `system.query_log` 膨胀。

请注意，大多数 LLM 模型都会将嵌入向量作为原生浮点数列表或 NumPy 数组返回。
因此，我们建议 Python 应用程序使用以下方式，以二进制形式绑定参考向量参数：

```python theme={null}
search_v = openai_client.embeddings.create(input = "[Good Books]", model='text-embedding-3-large', dimensions=1536).data[0].embedding

params = {'$search_v_binary$': np.array(search_v, dtype=np.float32).tobytes()}
result = chclient.query(
   "SELECT id FROM items
    ORDER BY cosineDistance(vector, reinterpret($search_v_binary$, 'Array(Float32)'))
    LIMIT 10"
    parameters = params)
```

在该示例中，参考向量会以原始二进制形式发送，并在服务器端重新解释为浮点数数组。
这样可以节省服务器端的 CPU 时间，并避免服务器日志和 `system.query_log` 过度膨胀。

<div id="administration">
  #### 管理与监控
</div>

向量相似度索引的磁盘占用大小可从 [system.data\_skipping\_indices](/zh/reference/system-tables/data_skipping_indices) 获取：

```sql theme={null}
SELECT database, table, name, formatReadableSize(data_compressed_bytes)
FROM system.data_skipping_indices
WHERE type = 'vector_similarity';
```

示例输出：

```result theme={null}
┌─database─┬─table─┬─name─┬─formatReadab⋯ssed_bytes)─┐
│ default  │ tab   │ idx  │ 348.00 MB                │
└──────────┴───────┴──────┴──────────────────────────┘
```

<div id="differences-to-regular-skipping-indexes">
  #### 与常规跳过索引的差异
</div>

和所有常规[跳过索引](/zh/concepts/features/performance/skip-indexes/skipping-indexes)一样，向量相似度索引也是基于粒度构建的，每个索引块由 `GRANULARITY = [N]` 个粒度组成 (对普通跳过索引来说，`[N]` 的默认值为 1) 。
例如，如果表的主索引粒度为 8192 (设置 `index_granularity = 8192`) ，且 `GRANULARITY = 2`，那么每个索引块将包含 16384 行。
但用于近似邻居搜索的数据结构和算法本质上是面向行的。
它们会存储一组行的紧凑表示，并且也会为向量搜索查询返回行。
因此，向量相似度索引的行为方式与普通跳过索引相比，会表现出一些相当不直观的差异。

当用户在某一列上定义向量相似度索引时，ClickHouse 会在内部为每个索引块创建一个向量相似度“子索引”。
这里所说的“局部”是指，子索引只了解其所属索引块中的行。
沿用前面的示例，假设某一列有 65536 行，那么会得到四个索引块 (跨越八个粒度) ，并为每个索引块创建一个向量相似度子索引。
从理论上讲，一个子索引可以直接返回其索引块内距离最近的 N 个点对应的行。
对于设置了 `vector_search_with_rescoring = 1` 的查询，如果查询计划允许这种优化，ClickHouse 就可以在基于存储向量计算最终距离之前，先利用这些行位置过滤行。
如果不使用重评分，ClickHouse 会通过虚拟列 `_distance` 直接使用向量索引中的距离。
这两种模式仍都会使用周围的粒度范围来调度读取，这不同于常规跳过索引，后者是在索引块级别跳过数据的。

`GRANULARITY` 参数决定会创建多少个向量相似度子索引。
`GRANULARITY` 值越大，创建的向量相似度子索引就越少，但每个子索引也越大；极端情况下，一列 (或该列的数据分区片段) 只会有一个子索引。
在这种情况下，这个子索引对该列的所有行都具有“全局”视角，并且可以直接返回该列 (或数据分区片段) 中包含相关行的所有粒度 (这样的粒度最多不超过 `LIMIT [N]` 个) 。
在 `vector_search_with_rescoring = 1` 时，ClickHouse 随后可以读取匹配的行位置，并为这些行计算精确距离。
当 `GRANULARITY` 值较小时，每个子索引最多可以返回 `LIMIT N` 个候选行。
因此，可能需要读取并进行后过滤的候选行会更多。
请注意，这两种情况下的搜索精度同样高，区别只在于处理性能。
通常建议为向量相似度索引使用较大的 `GRANULARITY`，只有在出现向量相似度结构内存占用过高等问题时，才退回到较小的 `GRANULARITY` 值。
如果未为向量相似度索引指定 `GRANULARITY`，默认值为 1 亿。

<div id="approximate-nearest-neighbor-search-example">
  #### 示例
</div>

查询：

```sql title="Query" theme={null}
CREATE TABLE tab
(
    id Int32,
    vec Array(BFloat16),
    INDEX idx vec TYPE vector_similarity('hnsw', 'L2Distance', 2)
)
ENGINE = MergeTree
ORDER BY id;

INSERT INTO tab VALUES (0, [1.0, 0.0]), (1, [1.1, 0.0]), (2, [1.2, 0.0]), (3, [1.3, 0.0]), (4, [1.4, 0.0]), (5, [1.5, 0.0]), (6, [0.0, 2.0]), (7, [0.0, 2.1]), (8, [0.0, 2.2]), (9, [0.0, 2.3]), (10, [0.0, 2.4]), (11, [0.0, 2.5]);

WITH [0., 2.] AS reference_vec
SELECT id, vec
FROM tab
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 3;
```

```result title="Response" theme={null}
   ┌─id─┬─vec─────┐
1. │  6 │ [0,2]   │
2. │  7 │ [0,2.1] │
3. │  8 │ [0,2.2] │
   └────┴─────────┘
```

更多使用近似向量搜索的示例数据集如下：

* [LAION-400M](/zh/get-started/sample-datasets/laion)
* [LAION-5B](/zh/get-started/sample-datasets/laion5b)
* [dbpedia](/zh/get-started/sample-datasets/dbpedia)
* [hackernews](/zh/get-started/sample-datasets/hacker-news-vector-search)

<div id="vector-search-with-quantized-codecs">
  ### 使用量化编解码器进行向量搜索
</div>

<ExperimentalBadge />

要启用 `Quantized` 编解码器，请先启用 `SET allow_experimental_codecs = 1`。
如果你遇到问题，请在 [ClickHouse repository](https://github.com/clickhouse/clickhouse/issues) 中提交 issue。

<div id="quantized-codecs-introduction">
  #### 简介
</div>

[向量相似度索引](#vector-similarity-index)通过遍历图来回答最近邻查询；当图可完全驻留于内存中时，其性能非常出色。
但以下两个因素限制了它的适用性：

* **构建成本高。** 构建向量相似度索引的成本很高。
* **内存占用高。** 除向量本身外，向量相似度索引还会占用大量内存，成为主要成本。
* **筛选。** 使用选择性较高的 `WHERE` 过滤器时，图遍历会变得低效：它要么无法到达满足谓词的少量行，要么必须检查数量严重失衡的候选项才能找到它们。

穷尽扫描则不受这两种限制：它不需要辅助数据结构，parts 可通过拼接自然合并，筛选也只会减少需要扫描的行数。
穷尽扫描最大的缺点是必须读取大量数据：扫描以完整 `Float32` 或 `BFloat16` 精度存储的向量时，由于必须从磁盘加载整个向量列，主要开销来自存储 I/O。

`Quantized` 列编解码器解决了这一问题。
它将每个向量存储两次：一次为原始全精度值，另一次为紧凑的量化表示。
向量搜索查询会先使用开销低且适合 SIMD 的距离函数扫描量化编码，生成由最有希望的结果候选项组成的候选列表。
第二步会根据全精度向量重新对这些候选项进行排序。
与扫描原始向量相比，初始扫描量化编码时从存储中读取的字节数要少得多。

该编解码器非常适合 ClickHouse，因为开销最大的部分 (扫描) 正是 ClickHouse engine 擅长处理的任务：

* **向量化。** 扫描内核利用 SIMD 指令降低 CPU 消耗。
* **跨核心和 parts 并行。** 扫描很容易并行化：可同时在所有可用线程和表的所有 parts 上计算距离。
* **分布式。** 在分片集群中，工作会分发到各台机器：每个分片并行扫描自己的数据部分，协调器再合并候选列表。
* **无需额外构建成本。** 写入向量时会生成量化编码。无需构建、调优或重建额外索引，因此数据写入表后即可搜索。

<div id="quantized-codecs-declaring">
  #### 应用编解码器
</div>

为 `Array(Float32)` (或 `Array(Float64)` / `Array(BFloat16)`) 列指定 `Quantized(...)` 编解码器。

```sql theme={null}
SET allow_experimental_codecs = 1;

CREATE TABLE vectors
(
    id UInt32,
    vec Array(BFloat16) CODEC(Quantized('rabitq', 1536))
)
ENGINE = MergeTree
ORDER BY ...;
```

无法在之后通过 `ALTER TABLE` 添加或修改该编解码器。

<div id="quantized-codecs-methods">
  #### 量化方法
</div>

每种量化方法在大小和准确性之间各有不同的权衡。
`dimensions` 参数表示向量长度。

* `Quantized('rabitq', dimensions)` — 每个坐标使用一个符号位，再加上一个无偏余弦校正因子 (`dimensions/8 + 4` 字节) 。体积小、`popcount` 成本低，是一个很强的默认选择。仅支持 `cosineDistance`。
* `Quantized('turboquant', dimensions)` — 每个坐标使用两位 (1 位 MSE 编码和 1 位残差编码) ，可获得保真度更高的候选结果 (`dimensions/4 + 4` 字节) 。仅支持 `cosineDistance`。
* `Quantized('int8', dimensions)` — 每个坐标使用一个 `Int8` 编码，再加上向量范数 (`dimensions + 4` 字节) ；体积最大，但也是最忠实的平坦编码。支持 `L2Distance` 和 `cosineDistance`。
* `Quantized('prefix', dimensions, leading_dimensions, 'int8'|'bf16')` — Matryoshka：只保留前 `leading_dimensions` 个坐标，并存为 `Int8` (带每向量标度) 或 `BFloat16`。适用于使用 Matryoshka Representation Learning 训练的嵌入向量，可生成极小的编码。支持 `L2Distance` 和 `cosineDistance`。
* `Quantized('product', dimensions, nbits, m)` — Product Quantization：按分片使用通过 k-means 训练得到的码本；每个向量会变成 `m` 个、每个 `nbits` 位的编码 (因此 `dimensions` 必须是 `m` 的倍数) 。这是最紧凑的方案，且单位字节召回率最高，但代价是在 insert 时需要执行训练步骤。支持 `L2Distance` 和 `cosineDistance`。

`rabitq` 和 `turboquant` 要求 `dimensions` 必须是 8 的倍数。

<div id="quantized-codecs-searching">
  #### 使用编解码器
</div>

常规的 top-`k` 查询形态会自动使用该编解码器 (请参阅[精确搜索](#exact-nearest-neighbor-search)) ：

```sql theme={null}
WITH [...] AS reference_vec
SELECT id
FROM vectors
ORDER BY cosineDistance(vec, reference_vec) ASC
LIMIT 10
SETTINGS vector_search_use_quantized_codes = 1;
```

将 `vector_search_use_quantized_codes = 1` 设为启用后，优化器可将查询重写为两阶段扫描。
该设置默认关闭。
未启用此设置时，相同查询会对原始向量执行常规精确扫描。
所有方法均支持距离函数 `cosineDistance`；其中 `int8`、`prefix` 和 `product` 方法还额外支持 `L2Distance` 距离函数。

设置 `vector_search_index_fetch_multiplier` 用于指定相对于查询 `LIMIT` 要入围的候选数量。
较大的乘数可提高召回率，但会增加重评分开销。
默认值为 `1` (不进行过采样) ；通常需要将其提高到 (例如) `10`，才能获得良好的召回率。

<div id="approximate-nearest-neighbor-search-qbit">
  ### 量化位 (QBit)
</div>

加速精确向量搜索的一种常见方法是使用较低精度的[浮点数据类型](/zh/reference/data-types/float)。
例如，如果向量存储为 `Array(BFloat16)` 而不是 `Array(Float32)`，数据大小会减少一半，查询运行时间预计也会相应缩短。
这种方法称为量化。虽然它能加快计算速度，但即使对所有向量进行穷尽扫描，也可能会降低结果的准确性。

使用传统量化时，我们在搜索和数据存储这两个阶段都会损失精度。以上述示例为例，我们存储的是 `BFloat16` 而不是 `Float32`，这意味着即使之后有需要，也无法再进行更高精度的搜索。另一种方案是同时存储两份数据：一份量化后的数据和一份全精度数据。虽然这样可行，但会带来冗余存储。设想这样一种场景：原始数据为 `Float64`，并且希望使用不同精度 (16 位、32 位或完整 64 位) 进行搜索。这样一来，就需要存储三份独立的数据副本。

ClickHouse 提供了 Quantized Bit (`QBit`) 数据类型，通过以下方式解决这些限制：

1. 存储原始的全精度数据。
2. 支持在查询时指定量化精度。

这是通过以按位分组的格式存储数据来实现的 (即将所有向量的第 i 位存储在一起) ，从而只读取所请求精度级别的数据。这样，你既能获得量化带来的 I/O 和计算量减少所带来的速度优势，又能在需要时使用全部原始数据。当选择最高精度时，搜索即为精确搜索。

要声明一个 `QBit` 类型的列，请使用以下语法：

```sql theme={null}
column_name QBit(element_type, dimension[, stride])
```

其中：

* `element_type` – 每个向量元素的数据类型。支持的类型有 `Int8`、`BFloat16`、`Float32` 和 `Float64`
* `dimension` – 每个向量中的元素个数
* `stride` – 可选。`dimension` 的一个除数，它会将各维度划分为 `dimension / stride` 个连续分组，并分别存储在独立的流中，因此如果只对前几个维度进行搜索，需要读取的流就更少 (对 Matryoshka 嵌入向量很有用) 。默认值为 `dimension`，此时该类型在字节级别上与非 stride 的 `QBit` 完全相同。详见 [`QBit` 数据类型页面](/zh/reference/data-types/qbit)。

<div id="qbit-create">
  #### 创建 `QBit` 表并插入数据
</div>

```sql theme={null}
CREATE TABLE fruit_animal (
    word String,
    vec QBit(Float64, 5)
) ENGINE = MergeTree
ORDER BY word;

INSERT INTO fruit_animal VALUES
    ('apple', [-0.99105519, 1.28887844, -0.43526649, -0.98520696, 0.66154391]),
    ('banana', [-0.69372815, 0.25587061, -0.88226235, -2.54593015, 0.05300475]),
    ('orange', [0.93338752, 2.06571317, -0.54612565, -1.51625717, 0.69775337]),
    ('dog', [0.72138876, 1.55757105, 2.10953259, -0.33961248, -0.62217325]),
    ('cat', [-0.56611276, 0.52267331, 1.27839863, -0.59809804, -1.26721048]),
    ('horse', [-0.61435682, 0.48542571, 1.21091247, -0.62530446, -1.33082533]);
```

<div id="qbit-search">
  #### 使用 `QBit` 进行向量搜索
</div>

下面使用 L2 距离查找与表示单词 'lemon' 的向量最接近的邻居。距离函数中的第三个参数用于指定精度 (以位为单位) ——值越高，精度越高，但所需计算也越多。

你可以在[这里](/zh/reference/data-types/qbit#vector-search-functions)查看 `QBit` 支持的所有距离函数。

**全精度搜索 (64 位) ：**

```sql theme={null}
SELECT
    word,
    L2DistanceTransposed(vec, [-0.88693672, 1.31532824, -0.51182908, -0.99652702, 0.59907770], 64) AS distance
FROM fruit_animal
ORDER BY distance;
```

```text theme={null}
   ┌─word───┬────────────distance─┐
1. │ apple  │ 0.14639757188169716 │
2. │ banana │   1.998961369007679 │
3. │ orange │   2.039041552613732 │
4. │ cat    │   2.752802631487914 │
5. │ horse  │  2.7555776805484813 │
6. │ dog    │   3.382295083120104 │
   └────────┴─────────────────────┘
```

**低精度搜索：**

```sql theme={null}
SELECT
    word,
    L2DistanceTransposed(vec, [-0.88693672, 1.31532824, -0.51182908, -0.99652702, 0.59907770], 12) AS distance
FROM fruit_animal
ORDER BY distance;
```

```text theme={null}
   ┌─word───┬───────────distance─┐
1. │ apple  │  0.757668703053566 │
2. │ orange │ 1.5499475034938677 │
3. │ banana │ 1.6168396735102937 │
4. │ cat    │  2.429752230904804 │
5. │ horse  │  2.524650475528617 │
6. │ dog    │   3.17766975527459 │
   └────────┴────────────────────┘
```

注意，使用 12 位量化时，我们能在加快查询执行速度的同时，较好地近似这些距离。相对顺序基本保持一致，'apple' 仍然是最接近的匹配项。

<div id="qbit-performance">
  #### 性能考量
</div>

`QBit` 的性能优势来自 I/O 操作减少：使用较低精度时，需要从存储中读取的数据更少。此外，当 `QBit` 包含 `Float32` 数据且精度参数为 16 或更低时，由于计算量减少，还能获得额外的性能收益。精度参数直接决定了准确性与速度之间的权衡：

* **更高精度** (更接近原始数据位宽) ：结果更准确，但查询更慢
* **更低精度**：查询更快，但结果是近似值，内存占用更低

<div id="references">
  ### 参考资料
</div>

博客文章：

* [ClickHouse 向量搜索 - 第 1 部分](https://clickhouse.com/blog/vector-search-clickhouse-p1)
* [ClickHouse 向量搜索 - 第 2 部分](https://clickhouse.com/blog/vector-search-clickhouse-p2)
* [我们构建了一个可让你在查询时选择精度的向量搜索引擎](https://clickhouse.com/blog/qbit-vector-search)
