## 模块 2：使用 AUTO_RANDOM 实现自动分片

![demo_203-tidb-x-beyond-mysql.jpg 85%](https://lab-static.pingcap.com/images/2025/12/2/33bc3e42e21c28d8601cc5c8dcdb7ea51ee60ea4.jpg)

TiDB 提供了高度兼容 MySQL 的体验，但由于其分布式架构（即 “不仅仅是 MySQL”），仍存在一些重要差异。TiDB 最大的优势之一在于自动完成分片操作，无需手动进行分片管理。

如果表的主键使用 TiDB 的 **AUTO_RANDOM** 而非传统的、单调递增的 MySQL **AUTO_INCREMENT**，TiDB 可自动将数据分布到多个分片，有效缓解由 **AUTO_INCREMENT** 引发的写入热点问题。

### 步骤 1. 理解 AUTO_RANDOM 的行为

- 在 TiDB 中，可以通过在主键列上指定 `AUTO_RANDOM(<shard_bits>)` 来启用自动分片。在每个分片内，该列的自增长行为与 MySQL 的 **auto_increment** 类似。此特性要求该列必须为 BIGINT 类型（64 位）：其中首位为符号位，接下来一部分为分片位，其余为 **auto_increment** 递增值。

![AutoRandomValueStructure_aa_001_20240125.png 70%](https://lab-static.pingcap.com/images/2025/11/19/357034cd28824fe7d2950338331c5d0f3f2eb2c6.png)

- 例如，列定义为 `bigint AUTO_RANDOM(5)`，表示 TiDB 会使用 5 位作为分片位，总共可划分 2 的 5 次方，即 32 个正向和 32 个负向分片。

### 步骤 2. 查看 bookshop 表结构中的 AUTO_RANDOM 属性

- 在 [终端 1](ssh://monitor1-ssh#1) 再次连接到实验数据库。

  ```bash
  mysql \
   --comments -u '${LAB:SERVERLESS_CLUSTER_USERNAME}' -p'${LAB:SERVERLESS_CLUSTER_PASSWORD}' \
   -h ${LAB:SERVERLESS_CLUSTER_HOST} -P ${LAB:SERVERLESS_CLUSTER_PORT} \
   --ssl-mode=VERIFY_IDENTITY --ssl-ca=/etc/ssl/certs/ca-certificates.crt \
   -D ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}
  ```

- 查看用于创建 `books` 表的 SQL 脚本，可以看到 `id` 列必须为 BIGINT 类型才能使用 AUTO_RANDOM。

  ```sql
  SYSTEM cat create_bookshop_schema_in_current_db.sql | egrep -A18 'CREATE TABLE `books`' | egrep '(`books`|`id`)';
  ```

  ```output
  CREATE TABLE `books` (
      `id` bigint(20) AUTO_RANDOM,
      PRIMARY KEY (`id`)
  ```

- 查看 `books` 表的定义，可以发现主键 `id` 使用了 AUTO_RANDOM，同时还作为表级提示（hint）被声明。

  ```sql
  SHOW CREATE TABLE books\G
  ```

  ```output
  *************************** 1. row ***************************
  ...
    `id` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,
  ...
    PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */
  ...
  ```

- 还可以从 information schema 中确认 AUTO_RANDOM 的设置。你会发现默认的分片位数为 5。

  ```sql
  SELECT TABLE_NAME, TIDB_ROW_ID_SHARDING_INFO
  FROM information_schema.tables
  WHERE TABLE_NAME='books' AND TABLE_SCHEMA='${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}';
  ```

  ```output
  +------------+---------------------------+
  | TABLE_NAME | TIDB_ROW_ID_SHARDING_INFO |
  +------------+---------------------------+
  | books      | PK_AUTO_RANDOM_BITS=5     |
  +------------+---------------------------+
  ` rows in set (0.01 sec)
  ```

### 步骤 3. 说明 AUTO_RANDOM 的行为机制

- 在用于创建 `books` 表的脚本中，设置了系统变量 `allow_auto_random_explicit_insert`，允许向 AUTO_RANDOM 字段显式插入值。这使得将原系统的数据迁移到 TiDB 更加方便。

  ```sql
  SYSTEM head -n 1 create_bookshop_schema_in_current_db.sql;
  ```

  ```output
  SET @@allow_auto_random_explicit_insert = true;
  ```

- 因此，表中已有的 100 本书的 `id` 是通过 SQL 脚本显式赋值的，编号范围为 1 到 100，这些主键值并不是由 AUTO_RANDOM 机制自动生成的。

  ```sql
  SELECT MIN(id), MAX(id), COUNT(id) FROM books;
  ```

  ```output
  +---------+---------+-----------+
  | MIN(id) | MAX(id) | COUNT(id) |
  +---------+---------+-----------+
  |       1 |     100 |       100 |
  +---------+---------+-----------+
  1 row in set (0.01 sec)
  ```

- 当你插入新图书时，AUTO_RANDOM 功能会自动为主键实现分片。为观察这种行为，我们将通过多次事务分 10 次插入新书记录，将已有书籍的标题加前缀 `v2` 后写入。

  ```sql
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 1 and 10;
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 11 and 20;
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 21 and 30;
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 31 and 40;
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 41 and 50;
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 51 and 60;
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 61 and 70;
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 71 and 80;
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 81 and 90;
  INSERT INTO books (title, type, published_at, stock, price) SELECT CONCAT('v2_',title), type, NOW(), 0, price FROM books WHERE id BETWEEN 91 and 100;
  ```

  ```output
  Records: 10  Duplicates: 0  Warnings: 0
  ...
  Records: 10  Duplicates: 0  Warnings: 0
  ```

- 回顾一下，AUTO_RANDOM(5) 表示有 5 位用于分片，另有 1 位为符号位，总共分配 6 个最高有效位作为分片标识位。

- 通过将 `id` 右移 58 位，可以得到用于分片的 6 位高位，从而可以直观地看到数据分布到哪些分片。下方的 SQL 查询也会显示每个分片中的第一行 `id`，并仅聚焦于标题包含 `_v2` 的图书。在本例输出中，100 条数据跨越了 9 个分片，你自己的结果可能略有不同，实际会出现 **8**、**9** 或 **10** 个分片，因为之前 10 批插入各自使用了不同事务。

  ```sql
  SELECT
      MIN(id) first_id,
      id >> 58 as shard,
      COUNT(1) as rows_by_shard
  FROM books WHERE title LIKE 'v2_%'
  GROUP BY shard
  ORDER BY first_id;
  ```

  ```output
  +---------------------+-------+---------------+
  | first_id            | shard | rows_by_shard |
  +---------------------+-------+---------------+
  | 1729382256910270585 |     6 |            10 |
  | 2017612633061982359 |     7 |            10 |
  | 2594073385365405887 |     9 |            10 |
  | 4323455642275676261 |    15 |            10 |
  | 4611686018427388065 |    16 |            10 |
  | 5188146770730811573 |    18 |            10 |
  | 5764607523034235011 |    20 |            20 |
  | 6341068275337658479 |    22 |            10 |
  | 8070450532247929003 |    28 |            10 |
  +---------------------+-------+---------------+
  9 rows in set (0.01 sec)
  ```

### 步骤 4. 对比使用 AUTO_RANDOM 前后的区别

- 请自行理解下述查询的结果。请注意，因为 AUTO_RANDOM 随机生成主键值，输出结果可能与你的实际结果不同。

  ```sql
  SELECT 'books', MIN(id), MAX(id), COUNT(id)
  FROM books WHERE title NOT LIKE 'v2_%'
  UNION ALL
  SELECT 'books_v2', MIN(id), MAX(id), COUNT(id)
  FROM books WHERE title LIKE 'v2_%'
  ORDER BY 1;
  ```

  ```output
  +----------+---------------------+---------------------+-----------+
  | books    | MIN(id)             | MAX(id)             | COUNT(id) |
  +----------+---------------------+---------------------+-----------+
  | books    |                   1 |                 100 |       100 |
  | books_v2 | 1729382256910270585 | 8070450532247929012 |       100 |
  +----------+---------------------+---------------------+-----------+
  2 rows in set (0.01 sec)
  ```

### 步骤 5. 清理数据

- 仅保留前 100 本书，删除 `books` 表中的多余记录。

  ```sql
  DELETE FROM books WHERE id > 100;
  ANALYZE TABLE books;
  ```

  ```output
  Query OK, 100 rows affected (0.2 sec)
  ```

- 想要深入了解 **AUTO_RANDOM** 如何缓解并发写入热点，可参考由讲师主导的实验 [Clustered and Non-Clustered Indexes（聚簇索引与非聚簇索引）](/labs/dba_307_lab_ff0)。现在请保持数据库连接，前往下一个模块。
