## 模块 5: 向量搜索 (v8.5.0)

受 AI 进步和在高维空间中高效处理数据需求的驱动，TiDB 支持向量数据类型和数据库内向量搜索。

在本模块中，您将创建一个具有向量数据类型的表，并插入带有向量数据的记录，使用 HNSW 索引来提高向量搜索性能。

+ 您可以将 TiDB 用作向量存储，提供即时优势，包括：
  + 高效的近似最近邻搜索。
  + 无计划和非计划停机时间，具有无限扩展性。
  + 统一存储关系数据和向量数据，扩展到极大规模。

### 步骤 1. 创建一个具有向量数据类型的测试表

+ 创建一个具有向量数据类型的表，并向其中填充随机数据（32768 行）。表名为 `vs_example.vector_table`。请注意，输出中显示的向量数据类型列为 `embedding`。

  ```sql
  CREATE DATABASE vs_example;
  USE vs_example;
  DROP SEQUENCE IF EXISTS vs_seq1;
  CREATE SEQUENCE vs_seq1;
  DROP TABLE IF EXISTS vector_table;
  CREATE TABLE vector_table (
      id BIGINT AUTO_RANDOM PRIMARY KEY,
      doc TEXT,
      embedding VECTOR(3)
  );
  SOURCE populate_vector_table.sql
  DESC vector_table;
  SELECT COUNT(1) FROM vector_table;
  SHOW CREATE TABLE vector_table\G
  ```

  ```output
  ...
  Query OK, 16384 rows affected (0.06 sec)
  Records: 16384 Duplicates: 0  Warnings: 0
  Query OK, 0 rows affected (0.10 sec)

  +-----------+-----------+------+------+---------+-------+
  | Field     | Type      | Null | Key  | Default | Extra |
  +-----------+-----------+------+------+---------+-------+
  | id        | bigint    | NO   | PRI  | NULL    |       |
  | doc       | text      | YES  |      | NULL    |       |
  | embedding | vector(3) | YES  |      | NULL    |       |
  +-----------+-----------+------+------+---------+-------+
  3 rows in set (0.00 sec)

  +----------+
  | COUNT(1) |
  +----------+
  |    32768 |
  +----------+
  1 row in set (0.02 sec)

  *************************** 1. row ***************************
         Table: vector_table
  Create Table: CREATE TABLE `vector_table` (
    `id` bigint NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,
    `doc` text DEFAULT NULL,
    `embedding` vector(3) DEFAULT NULL,
    PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_rand_base] AUTO_RANDOM_BASE=625585 */
  1 row in set (0.01 sec)
  ```

### 步骤 2. 运行 K-最近邻搜索并检查执行计划

+ 查看以下查询。它显示了使用余弦距离的典型 K-最近邻搜索。

+ 该算法在数据集 - 表 `vector_table` 中查找参考节点 `[0.10752534,0.3524283,0.46134815]` 的最近节点（前 3 个）。由于在步骤 1 中使用了随机数据，您的结果将与示例输出不同。

  ```sql
  SELECT doc, embedding
  FROM vector_table
  ORDER BY VEC_COSINE_DISTANCE(embedding, '[0.10752534,0.3524283,0.46134815]')
  LIMIT 3;
  ```

  ```output
  +-------+-----------------------------------+
  | doc   | embedding                         |
  +-------+-----------------------------------+
  | 3343  | [0.10953534,0.3534283,0.46134815] |
  | 20717 | [0.18479975,0.59234893,0.7714548] |
  | 19597 | [0.20609,0.6679932,0.8865328]     |
  +-------+-----------------------------------+
  3 rows in set (0.02 sec)
  ```

+ 查看执行计划以验证查询中是否使用了 `FullTableScan`。考虑到表 `vector_table` 仅包含 32768 行，响应时间显著迅速，但在处理数十亿行时，潜在的性能影响是显著的，不能忽视。请注意解释输出底部的操作，其中估计行数 (`estRows`) 远大于 `3.00`。

  ```sql
  EXPLAIN
  SELECT doc, embedding
  FROM vector_table
  ORDER BY VEC_COSINE_DISTANCE(embedding, '[0.10752534,0.3524283,0.46134815]')
  LIMIT 3;
  ```

  ```output
  +--------------------------------+----------+-----------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  | id                             | estRows  | task      | access object      | operator info                                                                                                                                      |
  +--------------------------------+----------+-----------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  | Projection_14                  | 3.00     | root      |                    | vs_example.vector_table.doc, vs_example.vector_table.embedding                                                                                     |
  | └─TopN_7                       | 3.00     | root      |                    | Column#4, offset:0, count:3                                                                                                                        |
  |   └─Projection_15              | 3.00     | root      |                    | vs_example.vector_table.doc, vs_example.vector_table.embedding, vec_cosine_distance(vs_example.vector_table.embedding, [0.11,0.35,0.46])->Column#4 |
  |     └─TableReader_13           | 3.00     | root      |                    | data:TopN_12                                                                                                                                       |
  |       └─TopN_12                | 3.00     | cop[tikv] |                    | vec_cosine_distance(vs_example.vector_table.embedding, [0.11,0.35,0.46]), offset:0, count:3                                                        |
  |         └─TableFullScan_11     | 30179.00 | cop[tikv] | table:vector_table | keep order:false                                                                                                                                   |
  +--------------------------------+----------+-----------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  6 rows in set (0.00 sec)
  ```

### 步骤 3. 在向量列上添加 HNSW 索引

+ 启用 TiFlash 副本。

  ```sql
  ALTER TABLE vector_table SET TIFLASH REPLICA 1;
  ```

+ 在列 `embedding` 上创建 HNSW 向量索引。HNSW 索引在近似精度（大多数情况下大于 98%）方面提供了良好的性能。

  ```sql
  CREATE VECTOR INDEX embedding_idx ON vector_table ((VEC_COSINE_DISTANCE(embedding))) USING HNSW;
  ```

+ 确认列已完全索引。验证 `ROWS_STABLE_INDEXED` 为 `32768`。

  ```sql
  SELECT * FROM information_schema.tiflash_indexes
  WHERE tidb_table='vector_table'\G
  ```

  ```output
  *************************** 1. row ***************************
            TIDB_DATABASE: vs_example
               TIDB_TABLE: vector_table
                 TABLE_ID: 114
              COLUMN_NAME: embedding
               INDEX_NAME: embedding_idx
                COLUMN_ID: 3
                 INDEX_ID: 1
               INDEX_KIND: HNSW
      ROWS_STABLE_INDEXED: 32768
  ROWS_STABLE_NOT_INDEXED: 0
       ROWS_DELTA_INDEXED: 0
   ROWS_DELTA_NOT_INDEXED: 0
            ERROR_MESSAGE:
         TIFLASH_INSTANCE: 10.90.4.131:3930
  1 row in set (0.00 sec)
  ```

### 步骤 4. 针对索引表运行 K-最近邻查询

+ 针对表 `vector_table` 运行相同的 k-最近邻搜索。

  ```sql
  SELECT doc, embedding
  FROM vector_table
  ORDER BY VEC_COSINE_DISTANCE(embedding, '[0.10752534,0.3524283,0.46134815]')
  LIMIT 3;
  ```

  ```output
  +-------+------------------------------------+
  | doc   | embedding                          |
  +-------+------------------------------------+
  | 3343  | [0.10953534,0.3534283,0.46134815]  |
  | 20717 | [0.18479975,0.59234893,0.7714548]  |
  | 24641 | [0.21949558,0.73159087,0.94375384] |
  +-------+------------------------------------+
  3 rows in set (0.03 sec)
  ```

### 步骤 5. 查看执行计划

+ 查看执行计划以验证查询中是否使用了近似最近邻索引（解释输出中 `access object` 列下的 `index:embedding_idx(embedding)` 对象和 `operator info` 列下的 `annIndex:COSINE` 操作符）。在大规模生产环境中，表 `vector_table` 预计在服务此查询时将优于 `vector_table_noidx`。请注意解释输出底部的操作，其中估计行数 (`estRows`) 仅为 `3.00`，这比步骤 2 中的 `TableFullScan` 高效得多。除了索引优化外，您还可以在输出的 `task` 列中找到 `mpp[tiflash]` 任务，这是 TiDB 实现的列存储查询优化。

  ```sql
  EXPLAIN
  SELECT doc, embedding
  FROM vector_table
  ORDER BY VEC_COSINE_DISTANCE(embedding, '[0.10752534,0.3524283,0.46134815]')
  LIMIT 3;
  ```

  ```output
  +--------------------------------------+---------+--------------+----------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  | id                                   | estRows | task         | access object                                      | operator info                                                                                                                                      |
  +--------------------------------------+---------+--------------+----------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  | Projection_28                        | 3.00    | root         |                                                    | vs_example.vector_table.doc, vs_example.vector_table.embedding                                                                                     |
  | └─TopN_10                            | 3.00    | root         |                                                    | Column#5, offset:0, count:3                                                                                                                        |
  |   └─Projection_29                    | 3.00    | root         |                                                    | vs_example.vector_table.doc, vs_example.vector_table.embedding, vec_cosine_distance(vs_example.vector_table.embedding, [0.11,0.35,0.46])->Column#5 |
  |     └─TableReader_25                 | 3.00    | root         |                                                    | MppVersion: 2, data:ExchangeSender_24                                                                                                              |
  |       └─ExchangeSender_24            | 3.00    | mpp[tiflash] |                                                    | ExchangeType: PassThrough                                                                                                                          |
  |         └─Projection_26              | 3.00    | mpp[tiflash] |                                                    | vs_example.vector_table.doc, vs_example.vector_table.embedding                                                                                     |
  |           └─TopN_23                  | 3.00    | mpp[tiflash] |                                                    | Column#4, offset:0, count:3                                                                                                                        |
  |             └─Projection_27          | 3.00    | mpp[tiflash] |                                                    | vs_example.vector_table.doc, vs_example.vector_table.embedding, vec_cosine_distance(vs_example.vector_table.embedding, [0.11,0.35,0.46])->Column#4 |
  |               └─TableFullScan_22     | 3.00    | mpp[tiflash] | table:vector_table, index:embedding_idx(embedding) | keep order:false, annIndex:COSINE(embedding..[0.11,0.35,0.46], limit:3)                                                                            |
  +--------------------------------------+---------+--------------+----------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
  9 rows in set (0.00 sec)
  ```

+ 如前所述，HNSW 索引依赖于 TiFlash。如果您是 TiDB 列存储 TiFlash 的新手，请查看其他动手实验 - TiDB Cloud Serverless 入门或注册自学课程 [TiDB 简介](https://www.pingcap.com/education/course-introduction-to-tidb/)。继续下一个模块。

+ 与本模块中的 3 维向量不同，现实世界中的场景通常涉及更多维度的向量，例如由嵌入模型生成的 1536 维向量。我们还提供了专注于这些主题的动手实验 - [TiDB 的生成式 AI](/labs?category=Generative+AI+at+TiDB)。

+ 请继续下一个模块。
