# Lab 3: Optimizer

### Overview
+ In these exercises, you will examine the TiDB optimizer's execution plan for several different SELECT queries by using the EXPLAIN and EXPLAIN ANALYZE command.

### Notes
+ Click [pe-class-key-oregon.pem](lab-controller/class_303_get_ec2_key_for_lab.php?region=oregon) to download the private key file, which is required to log into the Control Machine for this lab.
+ Commands and SQL statements presented on this page are clickable for easy copy and paste.
+ Your environment may be different from the one shown on the lab guide, including but not limited to IP, port, username, password and directory. Example output is displayed in a gray background in this guide.
+ The `$` in your terminal window is an OS prompt. The `tidb>` or `mysql>` in your terminal window is SQL command prompt.

### Assumptions
+ The private key file is downloaded and placed under `~/.ssh/` folder locally. Relevant permission has been provided by [chmod 400 ~/.ssh/pe-class-key-oregon.pem](lab-controller/class_303_get_ec2_key_for_lab.php?region=oregon)
+ For Windows users, please refer to this [guide](https://github.com/pingcap-inc/tidb-course-labs/blob/master/README_303_class_setup_guide_for_student_lab_control_en.md) for the private key preparation.

## Exercise 1 
### Subject: Data Preparation

### Overview
+ In this exercise, you will import more experimental data into TiDB database for the subsequent experiments. We will create a database called universe.

### Duration
+ This exercise should take you approximately 5 minutes to complete.

### Tasks
+ Connect to TiDB session.
+ Execute the script `universeV2.sql`.
+ Make `universe` the current database.
+ Check the imported data.

### Solution
0. Login as user ec2-user on monitor1_address. If you do not possess the credentials to connect, kindly review the <kbd>Assumptions</kbd> section under the <kbd>OVERVIEW</kbd> tab. The returned message may be slightly different from what is displayed here. If prompted, answer `yes`.
   ```command
   $ ssh -i ~/.ssh/pe-class-key-oregon.pem ec2-user@monitor1_address
   ```
   ```output
      ,     #_
      ~\_  ####_        Amazon Linux 2
   ~~  \_#####\
   ~~     \###|       
   ~~       \#/ ___
      ~~       V~' '->
      ~~~         /    A newer version of Amazon Linux is available!
         ~~._.   _/
            _/ _/       Amazon Linux 2023, GA and supported until 2028-03-15.
         _/m/'           https://aws.amazon.com/linux/amazon-linux-2023/
   ```

1. Go to the universe directory, connect to TiDB, and use `test` database. 
   ```command
   cd universe/
   ```
   
   ```command
   $ mysql -h ${HOST_DB1_PRIVATE_IP} --port 4000 -u root
   ```

   ```command
   use test;
   ```
   ```output
   Database changed
   ```

2. Execute the script `universeV2.sql`. Enter the following command at the terminal prompt and receive the results shown:
   ```command
   source universeV2.sql
   ```
   
   ```output
   tidb> source universeV2.sql
   Query OK, 0 rows affected (0.00 sec)

   Query OK, 0 rows affected (0.00 sec)

   Query OK, 0 rows affected (0.54 sec)

   Database changed

   Query OK, 0 rows affected (0.53 sec)

   Query OK, 0 rows affected, 1 warning (0.00 sec)

   ... ...

   Query OK, 0 rows affected, 1 warning (0.51 sec)

   Query OK, 0 rows affected, 1 warning (2.03 sec)
   ```

3. Make `universe` the current database.
   ```command
   use universe;
   ```
   ```output
   tidb>use universe;
   Database changed
   ```

4. Check the imported data.
   ```command
   SHOW TABLES;
   SELECT COUNT(*) FROM stars;
   SELECT COUNT(*) FROM planets;
   SELECT COUNT(*) FROM moons;
   SELECT COUNT(*) FROM planet_categories;
   SELECT COUNT(*) FROM discoveryFacility;
   SELECT COUNT(*) FROM parameterReference;
   SELECT COUNT(*) FROM planetarySystem;
   ```

   ```output
   tidb>show tables;
   +--------------------+
   | Tables_in_universe |
   +--------------------+
   | discoveryFacility  |
   | moons              |
   | parameterReference |
   | planet_categories  |
   | planetarySystem    |
   | planets            |
   | stars              |
   +--------------------+
   7 rows in set (0.00 sec)

   tidb>select count(*) from stars;
   +----------+
   | count(*) |
   +----------+
   |        2 |
   +----------+
   1 row in set (0.00 sec)

   tidb>select count(*) from planets;
   +----------+
   | count(*) |
   +----------+
   |       10 |
   +----------+
   1 row in set (0.00 sec)

   tidb>select count(*) from moons;
   +----------+
   | count(*) |
   +----------+
   |       28 |
   +----------+
   1 row in set (0.00 sec)

   tidb>select count(*) from planet_categories;
   +----------+
   | count(*) |
   +----------+
   |        3 |
   +----------+
   1 row in set (0.00 sec)

   tidb>select count(*) from discoveryFacility;
   +----------+
   | count(*) |
   +----------+
   |       70 |
   +----------+
   1 row in set (0.00 sec)

   tidb>select count(*) from parameterReference;
   +----------+
   | count(*) |
   +----------+
   |     2055 |
   +----------+
   1 row in set (0.00 sec)

   tidb>select count(*) from planetarySystem;
   +----------+
   | count(*) |
   +----------+
   |    34713 |
   +----------+
   1 row in set (0.02 sec)
   ```

## Exercise 2
### Subject: Using EXPLAIN

### Overview
+ In this exercise, you will you examine the TiDB optimizer's execution plan for several different SELECT queries by using the EXPLAIN command.

### Duration
+ This exercise should take you approximately 15 minutes to complete.

### Tasks
+ Execute the following statement to view the EXPLAIN output for a query against the `parameterReference` table:
   ```command
   EXPLAIN SELECT * FROM parameterReference WHERE id=100;
   ```
   + What is the access method for this query?
   + How many rows must TiDB read to perform this query?
   + Does this access strategy result in good performance? Why?
+ Execute the following statement to view the EXPLAIN output for a query against the `parameterReference` table that uses an expression to determine the value of the ID field to search for:
   ```command
   EXPLAIN SELECT * FROM parameterReference WHERE id=100 + 5;
   ```
   + Is there any difference in the execution plan for this query, when compared to the query that you looked at in step 1? Why?
+ Execute the following statement that uses a different expression to determine the value of the `id` field to search for:
   ```command
   EXPLAIN SELECT * FROM parameterReference WHERE id - 5 =100;
   ```
   + Is there any difference in the execution plan for this query, when compared to the query that you looked at in the step 1 and 2? Why?
+ Display the optimizer cost for the query by viewing the EXPLAIN information that is available in JSON format.
   + What cost did the optimizer calculate for this query?
   + Which columns must TiDB access to resolve the query?
   + Which operations are carried out on TiKV?
   + Which operations are carried out on the TiDB Server?

### Solution
1. Execute the following statement to view the EXPLAIN output for a query against the `parameterReference` table.
   ```command
   EXPLAIN SELECT * FROM parameterReference WHERE id=100;
   ```
   ```output
   tidb>EXPLAIN SELECT * FROM parameterReference WHERE id=100;
   +-------------+---------+------+--------------------------+---------------+
   | id          | estRows | task | access object            | operator info |
   +-------------+---------+------+--------------------------+---------------+
   | Point_Get_1 | 1.00    | root | table:parameterReference | handle:100    |
   +-------------+---------+------+--------------------------+---------------+
   1 row in set (0.00 sec)
   ```
   + What is the access method for this query? Answer: `Point_Get`
   + How many rows must TiDB read to perform this query? Answer: 1
   + Does this access strategy result in good performance? Why? Answer: Yes. The `Point_Get` type applies when you compare all parts of a `PRIMARY KEY` or `UNIQUE Index` to constant values. In this example, the table has at the most one matching row. The optimizer treats this row's column values as constants and reads them only once. In TiDB, `Point_Get` does not require compiler to generate an execution plan. As data is stored in TiKV in key-value format, we can directly retrieve data using `Point_Get` when the `WHERE` condition is a primary key or unique index.

2. Execute the following statement to view the EXPLAIN output for a query against the `parameterReference` table that uses an expression to determine the value of the `id` field to search for.
    ```command
    EXPLAIN SELECT * FROM parameterReference WHERE id=100+5;
    ```
    ```output
    tidb>EXPLAIN SELECT * FROM parameterReference WHERE id=100+5;
    +-------------+---------+------+--------------------------+---------------+
    | id          | estRows | task | access object            | operator info |
    +-------------+---------+------+--------------------------+---------------+
    | Point_Get_5 | 1.00    | root | table:parameterReference | handle:105    |
    +-------------+---------+------+--------------------------+---------------+
    1 row in set (0.00 sec)
    ```
   +  Is there any difference in the execution plan for this query, when compared to the query that you looked at in step 1? Why? Answer: No. The optimizer evaluates the expression 100+5 before executing the query.
  
3. Execute the following statement that uses a different expression to determine the value of the `id` field to search for.
    ```command
    EXPLAIN SELECT * FROM parameterReference WHERE id-5=100;
    ```
    ```output
    tidb>EXPLAIN SELECT * FROM parameterReference WHERE id-5=100;
    +-------------------------+---------+-----------+--------------------------+---------------------------------------------------+
    | id                      | estRows | task      | access object            | operator info                                     |
    +-------------------------+---------+-----------+--------------------------+---------------------------------------------------+
    | TableReader_7           | 1644.00 | root      |                          | data:Selection_6                                  |
    | └─Selection_6           | 1644.00 | cop[tikv] |                          | eq(minus(universe.parameterreference.id, 5), 100) |
    |   └─TableFullScan_5     | 2055.00 | cop[tikv] | table:parameterReference | keep order:false                                  |
    +-------------------------+---------+-----------+--------------------------+---------------------------------------------------+
    3 rows in set (0.00 sec)
    ```
   + Is there any difference in the execution plan for this query, when compared to the query that you looked at in step 1 and 2? Why? Answer: Yes. The optimizer cannot reduce the expression `ID-5=100` to a single value before executing the query. Thus, it must scan the full table (type: TableFullScan). The number of rows is an estimate based on statistics and may not equal to the exact number of rows in the `parameterReference` table.
  
4. Display the optimizer cost for the query by viewing the EXPLAIN information that is available in JSON format. 
   ```command
   CREATE INDEX idx_host_name_ref ON planetarySystem(hostname, pl_name, ref_id);
   EXPLAIN FORMAT = "tidb_json" SELECT pl_name, sy_snum, sy_pnum FROM planetarySystem WHERE pl_name='11 Com b' AND hostname='11 Com b' AND ref_id=71\G
   DROP INDEX idx_host_name_ref on planetarySystem;
   ```
   ```output
   tidb>CREATE INDEX idx_host_name_ref ON planetarySystem(hostname, pl_name, ref_id);
   Query OK, 0 rows affected (6.03 sec)
   tidb>EXPLAIN FORMAT = "tidb_json" SELECT pl_name, sy_snum, sy_pnum FROM planetarySystem WHERE pl_name='11 Com b' AND hostname='11 Com b' AND ref_id=71\G
   *************************** 1. row ***************************
   TiDB_JSON: [
      {
         "id": "Projection_4",
         "estRows": "1.00",
         "taskType": "root",
         "operatorInfo": "universe.planetarysystem.pl_name, universe.planetarysystem.sy_snum, universe.planetarysystem.sy_pnum",
         "subOperators": [
               {
                  "id": "IndexLookUp_10",
                  "estRows": "1.00",
                  "taskType": "root",
                  "subOperators": [
                     {
                           "id": "IndexRangeScan_8(Build)",
                           "estRows": "1.00",
                           "taskType": "cop[tikv]",
                           "accessObject": "table:planetarySystem, index:idx_host_name_ref(hostname, pl_name, ref_id)",
                           "operatorInfo": "range:[\"11 Com b\" \"11 Com b\" 71,\"11 Com b\" \"11 Com b\" 71], keep order:false"
                     },
                     {
                           "id": "TableRowIDScan_9(Probe)",
                           "estRows": "1.00",
                           "taskType": "cop[tikv]",
                           "accessObject": "table:planetarySystem",
                           "operatorInfo": "keep order:false"
                     }
                  ]
               }
         ]
      }
   ]

   1 row in set (0.01 sec)
   tidb>DROP INDEX idx_host_name_ref ON planetarySystem;
   Query OK, 0 rows affected (0.52 sec)
   ```
   + What cost did the optimizer calculate for this query? Answer: 1.0 (the value of the "estRows" field in the JSON output. The cost info JSON object displays a more detailed breakdown of the cost.)
   + Which columns must TiDB access to resolve the query? Answer: The `universe.planetarysystem.pl_name`, `universe.planetarysystem.sy_snum`, `universe.planetarysystem.sy_pnum` columns (the elements in the JSON "operatorInfo" array field)
   + Which operations are carried out on TiKV? Answer: The `IndexRangeScan` and `TableRowIDScan` are carried out on TiKV (the value of the `taskType` field in the JSON output is `cop[tikv]`).
   + Which operations are carried out on the TiDB Server? Answer: The `IndexLookUp` is carried out on TiDB Server (the value of the "taskType" field in the JSON output is "root").

## Exercise 3
### Subject: Using EXPLAIN ANALYZE

### Overview
+ In this exercise, you will you examine the TiDB optimizer's execution plan for several different SELECT queries by using the EXPLAIN ANALYZE command.

### Duration
+ This exercise should take you approximately 15 minutes to complete.

### Tasks
+ Create a clustered primary key table `T1` and non-clustered primary key table `T2`:
   ```command
   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`)
   );
   ```
+ Insert 3 rows into the table `T1` and `T2`:
   ```command
   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');
   ```
+ Execute the following statement to view the `EXPLAIN ANALYZE` output for a query against the `T1` table that uses an expression to determine the value of the `id` field to search for:
   ```command
   SELECT id, name from T1;
   EXPLAIN ANALYZE SELECT * FROM T1 WHERE id=1\G
   ```
   + What is the access method for this query?
   + What is the actual execution time of this query？
   + sHow many times the operator(access method) was executed?

+ Execute the following statement to view the `EXPLAIN ANALYZE` output for a query against the `id` table that uses an expression to determine the value of the ID field to search for:
   ```command
   SELECT id, name from T1;
   EXPLAIN ANALYZE SELECT * FROM T1 WHERE id=4\G
   ```
   + Is there any difference in the execution plan for this query, when compared to the query that you looked at in the previous step? Why?

+ Execute the following statement to view the `EXPLAIN ANALYZE` output for a query against the `T2` table that uses an expression to determine the value of the `id` field to search for:
   ```command
   SELECT _tidb_rowid, id, name from T2;
   EXPLAIN ANALYZE SELECT * FROM T2 WHERE id=1\G
   ```
   + Is there any difference in the execution plan for this query, when compared to the query that you looked at in step 5? Why?

+ Execute the following statement to view the `EXPLAIN ANALYZE` output for a query against the `T2` table that uses an expression to determine the value of the `id` field to search for:
   ```command
   SELECT _tidb_rowid, id, name from T2;
   EXPLAIN ANALYZE SELECT * FROM T2 WHERE _tidb_rowid=2\G
   ```
   + Is there any difference in the execution plan for this query, when compared to the query that you looked at in step 7? Why?

+ Clean up the table `T1` and `T2`.

### Solution
1. Create a clustered primary key table `T1` and non-clustered primary key table `T2`.
   ```command
   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
   tidb>DROP TABLE IF EXISTS `T1`;
   Query OK, 0 rows affected (0.52 sec)

   tidb>CREATE TABLE `T1` (
      ->   `id` bigint AUTO_INCREMENT,
      ->   `name` varchar(20) NOT NULL DEFAULT '',
      ->     PRIMARY KEY (`id`),
      ->     KEY (`name`)
      -> );
   Query OK, 0 rows affected (0.53 sec)

   tidb>DROP TABLE IF EXISTS `T2`;
   Query OK, 0 rows affected (0.52 sec)

   tidb>CREATE TABLE `T2` (
      ->   `id` bigint AUTO_INCREMENT,
      ->   `name` varchar(20) NOT NULL DEFAULT '',
      ->     PRIMARY KEY (`id`) NONCLUSTERED,
      ->     KEY (`name`)
      -> );
   Query OK, 0 rows affected (0.53 sec)
   ```

2. Insert 3 rows into each of the table `T1` and `T2`.
   ```command
   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
   tidb>INSERT INTO T1 VALUES(NULL, 'Tom');
   Query OK, 1 row affected (0.02 sec)

   ... ...

   tidb>INSERT INTO T2 VALUES(NULL, 'Frank');
   Query OK, 1 row affected (0.00 sec)
   ```

3. Execute the following statement to view the `EXPLAIN ANALYZE` output for a query against the `T1` table that uses an expression to determine the value of the `id` field to search for.
   ```command
   SELECT id, name from T1;
   EXPLAIN ANALYZE SELECT * FROM T1 WHERE id=1\G
   ```
   ```output
   tidb>SELECT id, name from T1;
   +----+-------+
   | id | name  |
   +----+-------+
   |  1 | Tom   |
   |  2 | Jack  |
   |  3 | Frank |
   +----+-------+
   3 rows in set (0.00 sec)

   tidb>EXPLAIN ANALYZE SELECT * FROM T1 WHERE id=1\G
   *************************** 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)
   ```
   + What is the access method for this query? Answer: Point_Get
   + What is the actual execution time of this query? Answer: time:474.1µs
   + How many times the operator(access method) was executed? Answer: loops:2 times
  
4. Execute the following statement to view the EXPLAIN ANALYZE output for a query against the `id` table that uses an expression to determine the value of the ID field to search for.
   ```command
   SELECT id, name from T1;
   EXPLAIN ANALYZE SELECT * FROM T1 WHERE id=4\G
   ```
   ```output
   tidb>SELECT id, name from T1;
   +----+-------+
   | id | name  |
   +----+-------+
   |  1 | Tom   |
   |  2 | Jack  |
   |  3 | Frank |
   +----+-------+
   3 rows in set (0.00 sec)

   tidb>EXPLAIN ANALYZE SELECT * FROM T1 WHERE id=4\G
   *************************** 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)
   ```
   + Is there any difference in the execution plan for this query, when compared to the query that in the previously step? Why? Answer: The actual execution time of this query is 473.8µs, and the times the operator(access method) was executed is just once. This is because once we confirm that there is no row with `id=4` in the clustered primary key table, we know that the data we need is not stored in the table. However, in the previous step, even after we find the row with `id=3`, we still need to confirm again whether there are any rows that meet the condition, even though the primary key is `id`.

5. Execute the following statement to view the `EXPLAIN ANALYZE` output for a query against the `T2` table that uses an expression to determine the value of the `id` field to search for.
   
   ```command
   SELECT _tidb_rowid, id, name from T2;
   EXPLAIN ANALYZE SELECT * FROM T2 WHERE id=1\G
   ```
   ```output
   tidb>SELECT _tidb_rowid, id, name from T2;
   +-------------+----+-------+
   | _tidb_rowid | id | name  |
   +-------------+----+-------+
   |           2 |  1 | Tom   |
   |           4 |  3 | Jack  |
   |           6 |  5 | Frank |
   +-------------+----+-------+
   3 rows in set (0.00 sec)

   tidb>EXPLAIN ANALYZE SELECT * FROM T2 WHERE id=1\G
   *************************** 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)
   ```

   + Is there any difference in the execution plan for this query, when compared to the query that you looked at in step 3 and 4? Why? Answer: From the `Get:{num_rpc:2, total_time:565.6µs}` field, we find that the number of the Get RPC requests (num_rpc) sent to TiKV is 2. This is because `T2` is a non-clustered primary key table, and we have to use `Point_Get` to obtain the `_tidb_rowid` from the primary key `id` first, and then use the `_tidb_rowid` to obtain the required row from the table. Therefore, there needs to be two RPC requests.

6. Execute the following statement to view the `EXPLAIN ANALYZE` output for a query against the `T2` table that uses an expression to determine the value of the `id` field to search for.
   ```command
   SELECT _tidb_rowid, id, name from T2;
   EXPLAIN ANALYZE SELECT * FROM T2 WHERE _tidb_rowid=2\G
   ```
   ```output
   tidb>SELECT _tidb_rowid, id, name from T2;
   +-------------+----+-------+
   | _tidb_rowid | id | name  |
   +-------------+----+-------+
   |           2 |  1 | Tom   |
   |           4 |  3 | Jack  |
   |           6 |  5 | Frank |
   +-------------+----+-------+
   3 rows in set (0.00 sec)

   tidb>EXPLAIN ANALYZE SELECT * FROM T2 WHERE _tidb_rowid=2\G
   *************************** 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) 
   ```
   + Is there any difference in the execution plan for this query, when compared to the query that you looked at in step 5? Why? Answer: From the `Get:{num_rpc:1, total_time:487.5µs}` field, we can conclude that the number of the Get RPC requests (num_rpc) sent to TiKV is 1. Although T2 table is a non-clustered primary key table, if we use `_tidb_rowid` directly as the query condition, only one RPC request is needed.

7. Clean up the table `T1` and `T2`. 
   ```command
   DROP TABLE T1;
   DROP TABLE T2;
   ```
   ```output
   tidb>DROP TABLE T1;
   Query OK, 0 rows affected (0.52 sec)

   tidb>DROP TABLE T2;
   Query OK, 0 rows affected (0.53 sec)
   ```

## End of Exercises