## 模块 6：理解请求单位（RU）

![demo_203-tidb-x-ru.jpg 85%](https://lab-static.pingcap.com/images/2025/11/27/0bf1a7a4c5d0d9c7661042ae329bf1e089a59290.jpg)

本模块将帮助你深入理解请求单位（Request Units, RU），通过探索不同数据库操作对其的消耗来加深认识。那么，什么是 RU 呢？在 TiDB Cloud 中，你无需自行管理 TiDB 和 TiKV 节点，计费方式基于实际存储量和读写 RU 的消耗。RU 本质上是一种加权总和，用于衡量 SQL 请求所消耗的系统资源。

下表展示了 TiDB Cloud 中 RU 的粗略估算。不用担心如果还没完全理解这些数值，不会影响后续实验步骤。

| RU 类型   | 粗略定义                                                                 |
|----------|:-------------------------------------------------------------------------|
| 1 读 RU  | `0.5 x 批量 PointGet 数 + 0.125 x PointGet 数 + 64K 数据数 + 0.3 x CPU(ms)` |
| 1 写 RU  | `(0.5 x 批量 PointGet 数 + 0.5 x PointGet 数 + 2K 数据数) x 3`          |

你可以配置每秒最小和最大 RU 限制，自动控制集群弹性伸缩范围。详细信息见下表。

| 订阅方案             | Starter（入门版）       | Essential（基础版）           |
|:--------------------|-------------------------:|-----------------------------:|
| 免费额度            | 每月 5 GiB 与 5000 万 RU | 无                           |
| 最小 RU 每秒        | 0                        | 2K                           |
| 最大 RU 每秒        | 无保证                   | 100K                         |
| 集群自动伸缩        | 无保证                   | 2k ~ 100K RU 每秒            |
| 可用性              | 99.9%（单可用区）            | 99.99%（多可用区）               |

![payg.jpg 50%](https://lab-static.pingcap.com/images/2025/12/12/1077d4a1ea93f3ba5a38f93250ba42f30ee81bdb.jpg)

[请求单位（RU）](https://www.pingcap.com/tidb-cloud-starter-pricing-details/) 是 TiDB 中用于量化系统资源（如 CPU/IO）消耗的统一、抽象的计量单位。充分利用 TiDB Cloud 免费 Starter 集群，可以测试和估算 SQL 语句的实际 RU 用量，为你的决策提供支持。

本模块会手把手指导你如何获取 RU 信息。

### 步骤 1. 创建测试表

- 在 [Terminal 1](ssh://monitor1-ssh#1) 上，如果尚未连接 TiDB，请先连接：

  ```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
  ```

  ```output
  mysql>
  ```

- 在 `${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}` 数据库下创建测试表 `tbl_ru_1` 和 `tbl_ru_2`，以下 DDL 说明两表定义相同。

  ```sql
  CREATE DATABASE IF NOT EXISTS ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME};
  USE ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME};

  DROP TABLE IF EXISTS tbl_ru_1;
  CREATE TABLE tbl_ru_1 (
  `ID` bigint PRIMARY KEY AUTO_RANDOM,
  `pState` varchar (100) NOT NULL,
  `cState` tinyint (4) DEFAULT NULL,
  `created_at` DATETIME,
  `updated_at` DATETIME,
  `txt` varchar(64) DEFAULT 'TTLTEST',
  KEY `pcStateIndex`(`pState`, `cState`)
  ) PRE_SPLIT_REGIONS=4;

  DROP TABLE IF EXISTS tbl_ru_2;
  CREATE TABLE tbl_ru_2 (
  `ID` bigint PRIMARY KEY AUTO_RANDOM,
  `pState` varchar (100) NOT NULL,
  `cState` tinyint (4) DEFAULT NULL,
  `created_at` DATETIME,
  `updated_at` DATETIME,
  `txt` varchar(64) DEFAULT 'TTLTEST',
   KEY `pcStateIndex` (`pState`, `cState`)
   ) PRE_SPLIT_REGIONS=4;
  ```

  ```output
  ...
  Query OK, 0 rows affected, 1 warning (0.28 sec)
  ```

### 步骤 2. 熟悉监控控制台

- 进入 TiDB Cloud 控制台中的 [Clusters 页面](https://tidbcloud.com/project/clusters)，点击你的实验集群名称。如果是使用“Create New Cluster”新建的，集群名为 `${LAB:SERVERLESS_CLUSTER_NAME}`。

![Demo-RU-001.png 85%](https://lab-static.pingcap.com/images/2025/11/24/9c016ddc77e89cb9d9ae0669890d36cb316f6d20.png)

- 点击 `${LAB:SERVERLESS_CLUSTER_NAME}`，再点击左上角的 “Overview” (概览)。

![Demo-RU-002.png 85%](https://lab-static.pingcap.com/images/2025/11/24/8d064cdda27759df1640171cbf92f7d340e4364d.png)

- 展开 “Monitoring”（监控）菜单，选择 “Diagnosis”（诊断）。在此用户可以查看执行的 SQL 语句及其消耗的 RU 数量。

![Demo-RU-003.png 85%](https://lab-static.pingcap.com/images/2025/11/24/586655532161734f86ee41c826cd99221b3533ce.png)

- 接着，展开“Monitoring”菜单，选择 “Metrics”（度量），再点开 “Cluster Status”（集群状态）标签页。这里可以监控集群中每秒 RU 消耗和 QPS（每秒查询数）。

![Demo-RU-004.png 85%](https://lab-static.pingcap.com/images/2025/11/24/1be07d68448a466052182b702096201f01e83fbc.png)

- 请保持此页面开启，后续我们将用它来观察 SQL 语句的 RU 消耗。

### 步骤 3. 单线程执行插入负载

- 在 [Terminal 1](ssh://monitor1-ssh#1) 中，退出到操作系统命令行：

  ```sql
  EXIT;
  ```

- 接着创建 Python 虚拟环境：

  ```bash
  python3 -m venv ~/.venv
  ~/.venv/bin/pip install mysql-connector-python prettytable
  ```

- 编写一个 Python 脚本，实现单线程向表插入数据：

  ```bash
  cat > demo_insert_single_thread.py <<'EOF'
  import mysql.connector
  from prettytable import PrettyTable
  import datetime
  import time
  import threading
  import sys

  config = {
  'user': '${LAB:SERVERLESS_CLUSTER_USERNAME}',
  'password': '${LAB:SERVERLESS_CLUSTER_PASSWORD}',
  'host': '${LAB:SERVERLESS_CLUSTER_HOST}',
  'database': '${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}',
  'raise_on_warnings': False,
  'port': ${LAB:SERVERLESS_CLUSTER_PORT},
  }

  def database_worker(thread_id):
     try:
         num_loop = 100;

         # 建立连接
         conn = mysql.connector.connect(**config)
         # 创建游标
         cursor = conn.cursor(prepared=True)

         for n in range(num_loop):
             queries = ["INSERT INTO tbl_ru_1(pState, cState, created_at, updated_at) VALUES (ELT(1 + FLOOR(RAND() * 9), 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's0'), 1 + FLOOR(RAND() * 2), NOW(), NOW());"
                     ]
             num_query = 1;
             cursor.execute("BEGIN")

             for i in range(num_query):
                 cursor.execute(queries[i])

             cursor.execute("COMMIT")

             if n % 10 == 0 and n != 0:
                 print("%d rows have been inserted." % n)

         print("%d rows have been inserted." % num_loop)

         try:
             cursor.close()
             conn.close()
         except Error as e:
             pass

     except (Error, MySQLInterfaceError) as connect_err:
         print(f"Thread {thread_id}: Connection or execution error: {connect_err}")
  def main():
     num_threads = 1

     print(f"Starting {num_threads} database worker threads...")

     # 创建并启动线程
     threads = []
     for i in range(num_threads):
         thread = threading.Thread(
             target=database_worker,
             args=(i + 1,),
             daemon=True
         )
         threads.append(thread)
         thread.start()
         time.sleep(0.1)

     print(f"All {num_threads} threads started successfully!")

     try:
         for thread in threads:
             thread.join()
     except KeyboardInterrupt:
         print("\nReceived interrupt signal. Stopping all threads...")
         sys.exit(0)

  if __name__ == "__main__":
     main()
  EOF
  ls -l demo_insert_single_thread.py
  ```

  ```output
  -rw-rw-r-- 1 ubuntu ubuntu 2250 Nov 20 11:35 demo_insert_single_thread.py
  ```

- 在 [Terminal 1](ssh://monitor1-ssh#1) 上运行脚本，即开启 1 个线程向 `tbl_ru_1` 插入 100 行随机数据，耗时约 2 分钟。

  ```bash
  ~/.venv/bin/python demo_insert_single_thread.py
  ```

  ```output
  Starting 1 database worker threads...
  All 1 threads started successfully!
  10 rows have been inserted.
  20 rows have been inserted.
  ...
  100 rows have been inserted.
  ```

### 步骤 4. 查看 RU 消耗

- 回到 `${LAB:SERVERLESS_CLUSTER_NAME}` 集群的 Overview 页面，展开左侧 “Monitoring”，选择 “Metrics”，再切换到 “Cluster Status” 标签。设置时间范围为 "Past 15 minutes"，刷新间隔为 30s。等待 2-3 分钟后，可以在 “Query Per Second” 和 “Request Units” 面板看到近期的写入活动。此时可记录单线程写入过程中的每秒 RU 消耗。

![Demo-RU-005.png 85%](https://lab-static.pingcap.com/images/2025/11/24/37a67e7f484d93f87bf7684636f6b09a4f13da29.png)

- 然后，展开 “Monitoring”，选择 “Diagnosis”，进入 “SQL Statement” 标签。设置时间范围为“Past 15 minutes”，SQL类型过滤为 “Insert”。1-3 分钟后，即可看到刚刚执行过的 SQL 语句。请记录 `Executions Count`（执行次数）、`Total RU`（RU 总计）、`Mean RU`（平均 RU）。

- `Mean RU` 表示每次执行该 SQL 语句消耗的 RU（本例中为 2.55）。
- `Total RU` 表示在选定时间段内所有该 SQL 执行所消耗的 RU 总量（示例为 255.00，100 次插入对应）。

![Demo-RU-006.png 85%](https://lab-static.pingcap.com/images/2025/11/24/6ac06a5976dfb3ae4df234937f28b02e31973b25.png)

- 这里有个有趣的现象：对于如 TiDB 这样的线性扩展分布式系统，RU 消耗可预测性很强。我们将在接下来的两步验证它。

### 步骤 5. 10 倍工作线程测试插入负载

- 在 [Terminal 1](ssh://monitor1-ssh#1) 上，创建一个多线程写入脚本（10 个线程）：

  ```bash
  cat > demo_insert_multi_thread.py <<'EOF'
  import mysql.connector
  from prettytable import PrettyTable
  import datetime
  import time
  import threading
  import sys

  config = {
  'user': '${LAB:SERVERLESS_CLUSTER_USERNAME}',
  'password': '${LAB:SERVERLESS_CLUSTER_PASSWORD}',
  'host': '${LAB:SERVERLESS_CLUSTER_HOST}',
  'database': '${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}',
  'raise_on_warnings': False,
  'port': ${LAB:SERVERLESS_CLUSTER_PORT},
  }

  def database_worker(thread_id):
     try:
         num_loop = 100;

         # 建立连接
         conn = mysql.connector.connect(**config)

         # 创建游标
         cursor = conn.cursor(prepared=True)

         for n in range(num_loop):
             queries = ["INSERT INTO tbl_ru_2(pState, cState, created_at, updated_at) VALUES (ELT(1 + FLOOR(RAND() * 9), 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's0'), 1 + FLOOR(RAND() * 2), NOW(), NOW());"
                     ]
             num_query = 1;
             cursor.execute("BEGIN")
             for i in range(num_query):
                 cursor.execute(queries[i])
             cursor.execute("COMMIT")
             if n % 10 == 0 and n != 0:
                 print("Thread %d: %d rows have been inserted." % (thread_id, n))
         print("Thread %d: %d rows have been inserted." % (thread_id, num_loop))

         try:
             cursor.close()
             conn.close()
         except Error as e:
             pass

     except (Error, MySQLInterfaceError) as connect_err:
         print(f"Thread {thread_id}: Connection or execution error: {connect_err}")
  def main():
     num_threads = 10

     print(f"Starting {num_threads} database worker threads...")

     # 创建并启动线程
     threads = []
     for i in range(num_threads):
         thread = threading.Thread(
             target=database_worker,
             args=(i + 1,),
             daemon=True
         )
         threads.append(thread)
         thread.start()
         time.sleep(0.1)

     print(f"All {num_threads} threads started successfully!")

     try:
         for thread in threads:
             thread.join()
     except KeyboardInterrupt:
         print("\nReceived interrupt signal. Stopping all threads...")
         sys.exit(0)

  if __name__ == "__main__":
     main()
  EOF
  ls -l demo_insert_multi_thread.py
  ```

  ```output
  -rw-rw-r-- 1 ubuntu ubuntu 2212 Nov 21 09:03 demo_insert_multi_thread.py
  ```

- 运行脚本，10 个线程同时操作，每个线程各插入 100 行至与 `tbl_ru_1` 结构相同的 `tbl_ru_2` 表。

  ```bash
  ~/.venv/bin/python demo_insert_multi_thread.py
  ```

  ```output
  Starting 10 database worker threads...
  ...
  Thread 9: 100 rows have been inserted.
  Thread 6: 100 rows have been inserted.
  Thread 7: 100 rows have been inserted.
  Thread 8: 100 rows have been inserted.
  ```

### 步骤 6. 再次查看 RU 消耗

- 回到 `${LAB:SERVERLESS_CLUSTER_NAME}` 集群 Overview 页面，展开“Monitoring”，进入“Metrics - Cluster Status”，时间范围 15 分钟、刷新频率 30 秒。等待 2-3 分钟后，可观察到多线程写入时 RU 消耗，记录相关数据。

![Demo-RU-007.png 85%](https://lab-static.pingcap.com/images/2025/11/24/a3d5f12c2f36c28700d1ed35e56f85e9b78d98f0.png)

- 然后，查看 “Diagnosis” 下 “SQL Statement” 标签，并用 “Insert” 类型进行筛选记录。

![Demo-RU-008.png 85%](https://lab-static.pingcap.com/images/2025/11/24/460a92179f90a203c65482ae697f7265a242ffae.png)

- 当以 10 个并发线程运行相同负载时，Mean RU 基本不变，而 Total RU 总量会变为单线程的 10 倍。示例中 Mean RU 仍为约 2.55，Total RU 达 2.55K 左右。

### 步骤 7. 用 EXPLAIN 查看 SQL 的 RU 信息

- Web 监控可能有 2-3 分钟延迟，而 `EXPLAIN ANALYZE` 可以立即获得 SQL 查询的 RU 消耗情况。

- 在 [Terminal 1](ssh://monitor1-ssh#1) 连接 TiDB：

  ```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}
  ```

- 运行以下 `explain analyze` 查询：

  ```sql
  PAGER less -S
  EXPLAIN ANALYZE
  SELECT MAX(created_at)
  FROM tbl_ru_1;
  ```

- 输出结果中使用键盘左右键，定位到 `execution info` 列。注意第一行中 `RU:` 后的数值，这即是本次查询实际消耗的 RU。下例中为 `RU:0.581812`（实际数值会有小幅差异）。

  ```output
  ... -----------+---------------+----------------------------------------------------->
  ...  task      | access object | execution info                                      >
  ... -----------+---------------+----------------------------------------------------->
  ...  root      |               | time:1.39ms, loops:2, RU:0.581812
  ```

- 按 `Q` 键退出输出。

### 步骤 8. 实验清理

- 在 [Terminal 1](ssh://monitor1-ssh#1) 上，删除 `${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}` 下的 `tbl_ru_1` 和 `tbl_ru_2`。

  ```sql
  NOPAGER;
  USE ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME};
  DROP TABLE tbl_ru_1;
  DROP TABLE tbl_ru_2;
  ```

  ```output
  Query OK, 0 rows affected (0.55 sec)
  ...
  Query OK, 0 rows affected (0.63 sec)
  ```

- 掌握 RU 后，你可以评估 SQL 语句的 RU 消耗，按 RU 对比 TiDB Cloud 各类订阅方案，选择适合自己业务场景的套餐。详细 RU 及定价信息请参考[官方文档](https://www.pingcap.com/tidb-cloud-starter-pricing-details/)。现在，请进入下一个模块。
