## 模块 3：使用 EXPLAIN ANALYZE 命令

+ 在本模块中，你将通过使用 EXPLAIN ANALYZE 命令，研究 TiDB 优化器针对几种不同 SELECT 查询的执行计划。

### 步骤 1. 创建表

+ 创建一个聚簇主键表 `T1` 和非聚簇主键表 `T2`。

   ```sql
   DROP TABLE IF EXISTS `T1`;
   CREATE TABLE `T1` (
     `id` bigint AUTO_INCREMENT,
     `name` varchar(20) NOT NULL DEFAULT '',
      PRIMARY KEY (`id`),
      KEY (`name`)
   );
   DROP TABLE IF EXISTS `T2`;
   CREATE TABLE `T2` (
     `id` bigint AUTO_INCREMENT,
     `name` varchar(20) NOT NULL DEFAULT '',
      PRIMARY KEY (`id`) NONCLUSTERED,
      KEY (`name`)
   );
   ```

   ```output
   Query OK, 0 rows affected (0.53 sec)
   ```

### 步骤 2. 向表中插入数据

   ```sql
   INSERT INTO T1 VALUES(NULL, 'Tom');
   INSERT INTO T1 VALUES(NULL, 'Jack');
   INSERT INTO T1 VALUES(NULL, 'Frank');
   INSERT INTO T2 VALUES(NULL, 'Tom');
   INSERT INTO T2 VALUES(NULL, 'Jack');
   INSERT INTO T2 VALUES(NULL, 'Frank');
   ```

   ```output
   Query OK, 1 row affected (0.00 sec)
   ```

### 步骤 3. 查看表 `T1` 的 `EXPLAIN ANALYZE` 输出

   ```sql
   SELECT id, name from T1;
   EXPLAIN ANALYZE SELECT * FROM T1 WHERE id=1\G
   ```

   ```output
   +----+-------+
   | id | name  |
   +----+-------+
   |  1 | Tom   |
   |  2 | Jack  |
   |  3 | Frank |
   +----+-------+
   3 rows in set (0.00 sec)

   *************************** 1. row ***************************
               id: Point_Get_1
          estRows: 1.00
          actRows: 1
             task: root
    access object: table:T1
   execution info: time:474.1µs, loops:2, RU:0.273086, Get:{num_rpc:1, total_time:443.5µs}, total_process_time: 67.5µs, total_wait_time: 56µs, tikv_wall_time: 149.5µs, scan_detail: {total_process_keys: 1, total_process_keys_size: 39, total_keys: 1, get_snapshot_time: 18.2µs, rocksdb: {block: {cache_hit_count: 2}}}
    operator info: handle:1
           memory: N/A
             disk: N/A
   1 row in set (0.00 sec)
   ```

+ 此查询的访问方式是什么？
  + 答案：Point_Get
+ 此查询的实际执行时间是多少？
  + 答案：time:474.1µs
+ 操作符（访问方式）执行了多少次？
  + 答案：loops:2 次

### 步骤 4. 查看表 `T1` 的 `EXPLAIN ANALYZE` 输出

   ```sql
   SELECT id, name from T1;
   EXPLAIN ANALYZE SELECT * FROM T1 WHERE id=4\G
   ```

   ```output
   +----+-------+
   | id | name  |
   +----+-------+
   |  1 | Tom   |
   |  2 | Jack  |
   |  3 | Frank |
   +----+-------+
   3 rows in set (0.00 sec)

   *************************** 1. row ***************************
               id: Point_Get_1
          estRows: 1.00
          actRows: 0
             task: root
    access object: table:T1
   execution info: time:473.8µs, loops:1, RU:0.272975, Get:{num_rpc:1, total_time:446.6µs}, total_process_time: 68.9µs, total_wait_time: 51.7µs, tikv_wall_time: 151.9µs, scan_detail: {total_keys: 1, get_snapshot_time: 17.3µs, rocksdb: {block: {cache_hit_count: 2}}}
    operator info: handle:4
           memory: N/A
             disk: N/A
   1 row in set (0.00 sec)
   ```

+ 与上一步的查询相比，此查询的执行计划有什么不同吗？为什么？
  + 答案：此查询的实际执行时间为 473.8µs，操作符（访问方法）只执行了一次。这是因为一旦我们确认聚簇主键表中没有 `id=4` 的行，就知道所需的数据不在表中。然而，在上一步中，即使我们找到了 `id=3`的行，由于主键是 `id`，我们仍然需要再次确认是否有其他符合条件的行。

### 步骤 5. 查看表 `T2` 的 `EXPLAIN ANALYZE` 输出

   ```sql
   SELECT _tidb_rowid, id, name from T2;
   EXPLAIN ANALYZE SELECT * FROM T2 WHERE id=1\G
   ```

   ```output
   +-------------+----+-------+
   | _tidb_rowid | id | name  |
   +-------------+----+-------+
   |           2 |  1 | Tom   |
   |           4 |  3 | Jack  |
   |           6 |  5 | Frank |
   +-------------+----+-------+
   3 rows in set (0.00 sec)

   *************************** 1. row ***************************
               id: Point_Get_1
          estRows: 1.00
          actRows: 1
             task: root
    access object: table:T2, index:PRIMARY(id)
   execution info: time:592.9µs, loops:2, RU:0.520601, Get:{num_rpc:2, total_time:565.6µs}, total_process_time: 57.8µs, total_wait_time: 53.1µs, tikv_wall_time: 147.1µs, scan_detail: {total_process_keys: 2, total_process_keys_size: 87, total_keys: 2, get_snapshot_time: 12.6µs, rocksdb: {block: {cache_hit_count: 8}}}
    operator info: 
           memory: N/A
             disk: N/A
   1 row in set (0.00 sec)
   ```

+ 与步骤 3 和步骤 4 中查看的查询相比，此查询的执行计划有什么不同吗？为什么？
  + 答案：从 `Get:{num_rpc:2, total_time:565.6µs}` 字段，我们发现发送到 TiKV 的 Get RPC 请求数（num_rpc）为 2。这是因为 `T2` 是一个非聚簇主键表，我们必须先使用 `Point_Get` 从主键 `id` 获取 `_tidb_rowid` ，然后再使用 `_tidb_rowid` 从表中获取所需的行。因此，需要两个 RPC 请求。

### 步骤 6. 查看表 `T2` 的 `EXPLAIN ANALYZE` 输出

   ```sql
   SELECT _tidb_rowid, id, name from T2;
   EXPLAIN ANALYZE SELECT * FROM T2 WHERE _tidb_rowid=2\G
   ```

   ```output
   +-------------+----+-------+
   | _tidb_rowid | id | name  |
   +-------------+----+-------+
   |           2 |  1 | Tom   |
   |           4 |  3 | Jack  |
   |           6 |  5 | Frank |
   +-------------+----+-------+
   3 rows in set (0.00 sec)

   *************************** 1. row ***************************
               id: Point_Get_1
          estRows: 1.00
          actRows: 1
             task: root
    access object: table:T2
   execution info: time:518.2µs, loops:2, RU:0.274932, Get:{num_rpc:1, total_time:487.5µs}, total_process_time: 72.8µs, total_wait_time: 53.5µs, tikv_wall_time: 158.9µs, scan_detail: {total_process_keys: 1, total_process_keys_size: 43, total_keys: 1, get_snapshot_time: 16.6µs, rocksdb: {block: {cache_hit_count: 4}}}
    operator info: handle:2
           memory: N/A
             disk: N/A
   1 row in set (0.00 sec) 
   ```

+ 与步骤 5 中查看的查询相比，此查询的执行计划有什么不同吗？为什么？
  + 答案：从 `Get:{num_rpc:1, total_time:487.5µs}` 字段，我们可以得出发送到 TiKV 的 Get RPC 请求数（num_rpc）为 1。尽管 T2 表是非聚簇主键表，但如果我们直接使用 `_tidb_rowid` 作为查询条件，只需要一个 RPC 请求。

### 步骤 7. 清理表 `T1` 和 `T2`

   ```sql
   DROP TABLE T1;
   DROP TABLE T2;
   ```

   ```output
   Query OK, 0 rows affected (0.53 sec)
   ```
