diff --git a/blocklist-control-plan.md b/blocklist-control-plan.md index 27b1c6507c5b7..87af8ac70517f 100644 --- a/blocklist-control-plan.md +++ b/blocklist-control-plan.md @@ -146,7 +146,7 @@ To remove one or more expressions from the blocklist, perform the following step In the following example, the `<` and `>` operators are added to the blocklist, and then the `>` operator is removed from the blocklist. -To judge whether the blocklist takes effect, observe the results of `EXPLAIN` (See [Optimize SQL statements using `EXPLAIN`](/query-execution-plan.md#optimize-sql-statements-using-explain)). +To judge whether the blocklist takes effect, observe the results of `EXPLAIN` (See [Optimize SQL statements using `EXPLAIN`](/query-execution-plan.md#explain-overview)). 1. The predicates `a < 2` and `a > 2` in the `WHERE` clause of the following SQL statement can be pushed down to TiKV. diff --git a/index-merge.md b/index-merge.md index ab4bc2e2c9365..9340a64646b80 100644 --- a/index-merge.md +++ b/index-merge.md @@ -30,13 +30,13 @@ explain select * from t where a = 1 or b = 1; In the above query, the filter condition is a `WHERE` clause that uses `OR` as the connector. Because you can use only one index per table, `a = 1` cannot be pushed down to the index `a`; neither can `b = 1` be pushed down to the index `b`. To ensure that the result is correct, the execution plan of `TableScan` is generated for the query: ``` -+---------------------+----------+-----------+------------------------------------------------------------+ -| id | count | task | operator info | -+---------------------+----------+-----------+------------------------------------------------------------+ -| TableReader_7 | 8000.00 | root | data:Selection_6 | -| └─Selection_6 | 8000.00 | cop[tikv] | or(eq(test.t.a, 1), eq(test.t.b, 1)) | -| └─TableScan_5 | 10000.00 | cop[tikv] | table:t, range:[-inf,+inf], keep order:false, stats:pseudo | -+---------------------+----------+-----------+------------------------------------------------------------+ ++-------------------------+----------+-----------+---------------+--------------------------------------+ +| id | estRows | task | access object | operator info | ++-------------------------+----------+-----------+---------------+--------------------------------------+ +| TableReader_7 | 8000.00 | root | | data:Selection_6 | +| └─Selection_6 | 8000.00 | cop[tikv] | | or(eq(test.t.a, 1), eq(test.t.b, 1)) | +| └─TableFullScan_5 | 10000.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++-------------------------+----------+-----------+---------------+--------------------------------------+ ``` The full table scan is inefficient when a huge volume of data exists in `t`, but the query returns only two rows at most. To handle such a scenario, `IndexMerge` is introduced in TiDB to access tables. @@ -46,14 +46,14 @@ The full table scan is inefficient when a huge volume of data exists in `t`, but `IndexMerge` allows the optimizer to use multiple indexes per table, and merge the results returned by each index before further operation. Take the [above query](#applicable-scenarios) as an example, the generated execution plan is shown as follows: ``` -+--------------------+-------+-----------+---------------------------------------------------------------+ -| id | count | task | operator info | -+--------------------+-------+-----------+---------------------------------------------------------------+ -| IndexMerge_11 | 2.00 | root | | -| ├─IndexScan_8 | 1.00 | cop[tikv] | table:t, index:a, range:[1,1], keep order:false, stats:pseudo | -| ├─IndexScan_9 | 1.00 | cop[tikv] | table:t, index:b, range:[1,1], keep order:false, stats:pseudo | -| └─TableScan_10 | 2.00 | cop[tikv] | table:t, keep order:false, stats:pseudo | -+--------------------+-------+-----------+---------------------------------------------------------------+ ++--------------------------------+---------+-----------+---------------------+---------------------------------------------+ +| id | estRows | task | access object | operator info | ++--------------------------------+---------+-----------+---------------------+---------------------------------------------+ +| IndexMerge_11 | 2.00 | root | | | +| ├─IndexRangeScan_8(Build) | 1.00 | cop[tikv] | table:t, index:a(a) | range:[1,1], keep order:false, stats:pseudo | +| ├─IndexRangeScan_9(Build) | 1.00 | cop[tikv] | table:t, index:b(b) | range:[1,1], keep order:false, stats:pseudo | +| └─TableRowIDScan_10(Probe) | 2.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++--------------------------------+---------+-----------+---------------------+---------------------------------------------+ ``` The structure of the `IndexMerge` execution plan is similar to that of the `IndexLookUp`, both of which consist of index scans and full table scans. However, the index scan part of `IndexMerge` might include multiple `IndexScan`s. When the primary key index of the table is the integer type, index scans might even include `TableScan`. For example: @@ -75,14 +75,14 @@ explain select * from t where a = 1 or b = 1; ``` ``` -+--------------------+-------+-----------+---------------------------------------------------------------+ -| id | count | task | operator info | -+--------------------+-------+-----------+---------------------------------------------------------------+ -| IndexMerge_11 | 2.00 | root | | -| ├─TableScan_8 | 1.00 | cop[tikv] | table:t, range:[1,1], keep order:false, stats:pseudo | -| ├─IndexScan_9 | 1.00 | cop[tikv] | table:t, index:b, range:[1,1], keep order:false, stats:pseudo | -| └─TableScan_10 | 2.00 | cop[tikv] | table:t, keep order:false, stats:pseudo | -+--------------------+-------+-----------+---------------------------------------------------------------+ ++--------------------------------+---------+-----------+---------------------+---------------------------------------------+ +| id | estRows | task | access object | operator info | ++--------------------------------+---------+-----------+---------------------+---------------------------------------------+ +| IndexMerge_11 | 2.00 | root | | | +| ├─TableRangeScan_8(Build) | 1.00 | cop[tikv] | table:t | range:[1,1], keep order:false, stats:pseudo | +| ├─IndexRangeScan_9(Build) | 1.00 | cop[tikv] | table:t, index:b(b) | range:[1,1], keep order:false, stats:pseudo | +| └─TableRowIDScan_10(Probe) | 2.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++--------------------------------+---------+-----------+---------------------+---------------------------------------------+ 4 rows in set (0.01 sec) ``` diff --git a/query-execution-plan.md b/query-execution-plan.md index aaa59cf0bf9c6..af7e45b52966c 100644 --- a/query-execution-plan.md +++ b/query-execution-plan.md @@ -8,156 +8,471 @@ aliases: ['/docs/stable/query-execution-plan/','/docs/v4.0/query-execution-plan/ Based on the details of your tables, the TiDB optimizer chooses the most efficient query execution plan, which consists of a series of operators. This document details the execution plan information returned by the `EXPLAIN` statement in TiDB. -## Optimize SQL statements using `EXPLAIN` +## `EXPLAIN` overview The result of the `EXPLAIN` statement provides information about how TiDB executes SQL queries: -- `EXPLAIN` works together with `SELECT`, `DELETE`, `INSERT`, `REPLACE`, and `UPDATE`. -- When you run the `EXPLAIN` statement, TiDB returns the final physical execution plan which is optimized by the SQL statement of `EXPLAIN`. In other words, `EXPLAIN` displays the complete information about how TiDB executes the SQL statement, such as in which order, how tables are joined, and what the expression tree looks like. For more information, see [`EXPLAIN` output format](#explain-output-format). -- TiDB also supports `EXPLAIN [options] FOR CONNECTION connection_id`, but with minor differences from MySQL. See [`EXPLAIN FOR CONNECTION`](#explain-for-connection). +- `EXPLAIN` works together with statements such as `SELECT` and `DELETE`. +- When you execute the `EXPLAIN` statement, TiDB returns the final optimized physical execution plan. In other words, `EXPLAIN` displays the complete information about how TiDB executes the SQL statement, such as in which order, how tables are joined, and what the expression tree looks like. +- For more information about each column of `EXPLAIN`, see [`EXPLAIN` Output Format](/sql-statements/sql-statement-explain.md). The results of `EXPLAIN` shed light on how to index the data tables so that the execution plan can use the index to speed up the execution of SQL statements. You can also use `EXPLAIN` to check if the optimizer chooses the optimal order to join tables. -## `EXPLAIN` output format +## Operator execution order -Currently, the `EXPLAIN` statement returns the following four columns: id, count, task, operator info. Each operator in the execution plan is described by the four properties. In the results returned by `EXPLAIN`, each row describes an operator. See the following table for details: +The execution plan in TiDB has a tree structure, with each node of the tree as an operator. Considering the concurrent execution of multiple threads in each operator, all operators consume CPU and memory resources to process data during the execution of a SQL statement. From this point of view, there is no execution order for the operator. -| Property Name | Description | -| -----| ------------- | -| id | The id of an operator, to identify the uniqueness of an operator in the entire execution plan. As of TiDB 2.1, the id includes formatting to show a tree structure of operators. The data flows from a child to its parent, and each operator has one and only one parent. | -| count | An estimation of the number of data items that the current operator outputs, based on the statistics and the execution logic of the operator | -| task | the task that the current operator belongs to. The current execution plan contains two types of tasks: 1) the **root** task that runs on the TiDB server; 2) the **cop** task that runs concurrently on the TiKV server. The topological relations of the current execution plan in the task level is that a root task can be followed by many cop tasks. The root task uses the output of cop task as the input. The cop task executes the tasks that TiDB pushes to TiKV. Each cop task scatters in the TiKV cluster and is executed by multiple processes. | -| operator info | The details about each operator. The information of each operator differs from others, see [Operator Info](#operator-info).| +However, from the perspective of which operators process a row of data first, the execution of a piece of data is in order. The following rule roughly summarizes this order: -### Example usage +**`Build` is always executed before `Probe` and always appears before `Probe`.** -Using the [bikeshare example database](/import-example-data.md): +The first half of this rule means: if an operator has multiple child nodes, the operator with the `Build` keyword at the end of the child node ID is always executed before the operator with the `Probe` keyword. The second half means: when TiDB shows the execution plan, the `Build` side always appears first, followed by the `Probe` side. + +The following examples illustrate this rule: + +{{< copyable "sql" >}} + +```sql +explain select * from t use index(idx_a) where a = 1; +``` ```sql -mysql> EXPLAIN SELECT count(*) FROM trips WHERE start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'; -+--------------------------+-------------+------+------------------------------------------------------------------------------------------------------------------------+ -| id | count | task | operator info | -+--------------------------+-------------+------+------------------------------------------------------------------------------------------------------------------------+ -| StreamAgg_20 | 1.00 | root | funcs:count(col_0) | -| └─TableReader_21 | 1.00 | root | data:StreamAgg_9 | -| └─StreamAgg_9 | 1.00 | cop | funcs:count(1) | -| └─Selection_19 | 8166.73 | cop | ge(bikeshare.trips.start_date, 2017-07-01 00:00:00.000000), le(bikeshare.trips.start_date, 2017-07-01 23:59:59.000000) | -| └─TableScan_18 | 19117643.00 | cop | table:trips, range:[-inf,+inf], keep order:false | -+--------------------------+-------------+------+------------------------------------------------------------------------------------------------------------------------+ -5 rows in set (0.00 sec) ++-------------------------------+---------+-----------+-------------------------+---------------------------------------------+ +| id | estRows | task | access object | operator info | ++-------------------------------+---------+-----------+-------------------------+---------------------------------------------+ +| IndexLookUp_7 | 10.00 | root | | | +| ├─IndexRangeScan_5(Build) | 10.00 | cop[tikv] | table:t, index:idx_a(a) | range:[1,1], keep order:false, stats:pseudo | +| └─TableRowIDScan_6(Probe) | 10.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++-------------------------------+---------+-----------+-------------------------+---------------------------------------------+ +3 rows in set (0.00 sec) ``` -Here you can see that the coprocesor (cop) needs to scan the table `trips` to find rows that match the criteria of `start_date`. Rows that meet this criteria are determined in `Selection_19` and passed to `StreamAgg_9`, all still within the coprocessor (i.e. inside of TiKV). The `count` column shows an approximate number of rows that will be processed, which is estimated with the help of table statistics. In this query it is estimated that each of the TiKV nodes will return `1.00` row to TiDB (as `TableReader_21`), which are then aggregated as `StreamAgg_20` to return an estimated `1.00` row to the client. +The `IndexLookUp_7` operator has two child nodes: `IndexRangeScan_5 (Build)` and `TableRowIDScan_6 (Probe)`. `IndexRangeScan_5 (Build)` appears first. + +To get a piece of data, first, you need to execute `IndexRangeScan_5 (Build)` to get a RowID. Then, use `TableRowIDScan_6 (Probe)` to get a complete row of data based on the RowID. + +The implication of the above rule is: for nodes at the same level, the operator that appears first might be executed first, and the operator that appears last might be executed last. This can be illustrated in the following example: + +{{< copyable "sql" >}} -The good news with this query is that most of the work is pushed down to the coprocessor. This means that minimal data transfer is required for query execution. However, the `TableScan_18` can be eliminated by adding an index to speed up queries on `start_date`: +```sql +explain select * from t t1 use index(idx_a) join t t2 use index() where t1.a = t2.a; +``` ```sql -mysql> ALTER TABLE trips ADD INDEX (start_date); -.. -mysql> EXPLAIN SELECT count(*) FROM trips WHERE start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'; -+------------------------+---------+------+--------------------------------------------------------------------------------------------------+ -| id | count | task | operator info | -+------------------------+---------+------+--------------------------------------------------------------------------------------------------+ -| StreamAgg_25 | 1.00 | root | funcs:count(col_0) | -| └─IndexReader_26 | 1.00 | root | index:StreamAgg_9 | -| └─StreamAgg_9 | 1.00 | cop | funcs:count(1) | -| └─IndexScan_24 | 8166.73 | cop | table:trips, index:start_date, range:[2017-07-01 00:00:00,2017-07-01 23:59:59], keep order:false | -+------------------------+---------+------+--------------------------------------------------------------------------------------------------+ -4 rows in set (0.01 sec) ++----------------------------------+----------+-----------+--------------------------+------------------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++----------------------------------+----------+-----------+--------------------------+------------------------------------------------------------------+ +| HashJoin_22 | 12487.50 | root | | inner join, inner:TableReader_26, equal:[eq(test.t.a, test.t.a)] | +| ├─TableReader_26(Build) | 9990.00 | root | | data:Selection_25 | +| │ └─Selection_25 | 9990.00 | cop[tikv] | | not(isnull(test.t.a)) | +| │ └─TableFullScan_24 | 10000.00 | cop[tikv] | table:t2 | keep order:false, stats:pseudo | +| └─IndexLookUp_29(Probe) | 9990.00 | root | | | +| ├─IndexFullScan_27(Build) | 9990.00 | cop[tikv] | table:t1, index:idx_a(a) | keep order:false, stats:pseudo | +| └─TableRowIDScan_28(Probe) | 9990.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | ++----------------------------------+----------+-----------+--------------------------+------------------------------------------------------------------+ +7 rows in set (0.00 sec) ``` -In the revisited `EXPLAIN` you can see the count of rows scanned has reduced via the use of an index. On a reference system, the query execution time reduced from 50.41 seconds to 0.01 seconds! +To complete the `HashJoin_22` operation, you need to execute `TableReader_26(Build)`, and then execute `IndexLookUp_29(Probe)`. + +When executing `IndexLookUp_29(Probe)`, you need to execute `IndexFullScan_27(Build)` and `TableRowIDScan_28(Probe)` one by one. Therefore, from the perspective of the whole execution link, `TableRowIDScan_28(Probe)` is the last one awoken to be executed. + +### Task overview + +Currently, calculation tasks of TiDB can be divided into two categories: cop tasks and root tasks. The cop tasks are performed by the Coprocessor in TiKV, and the root tasks are performed in TiDB. + +One of the goals of SQL optimization is to push the calculation down to TiKV as much as possible. The Coprocessor in TiKV supports most of the built-in SQL functions (including the aggregate functions and the scalar functions), SQL `LIMIT` operations, index scans, and table scans. However, all `Join` operations can only be performed as root tasks in TiDB. + +### Range query + +In the `WHERE`/`HAVING`/`ON` conditions, the TiDB optimizer analyzes the result returned by the primary key query or the index key query. For example, these conditions might include comparison operators of the numeric and date type, such as `>`, `<`, `=`, `>=`, `<=`, and the character type such as `LIKE`. + +> **Note:** +> +> - Currently, TiDB only supports the case where a comparison operator is connected by a column (at one end) and a constant value (at the other end), or the case that the calculation result is a constant. You cannot use query conditions like `year(birth_day) < 1992` as the index. +> - It is recommended to compare data of the same type; otherwise, additional `cast` operations are introduced, which causes the index to be unavailable. For example, regarding the condition `user_id = 123456`, if `user_id` is a string, you must define `123456` as a string constant. + +You can also use `AND` (intersection) and `OR` (union) to combine the range query conditions of one column. For a multi-dimensional composite index, you can use conditions in multiple columns. For example, regarding the composite index `(a, b, c)`: + +- When `a` is an equivalent query, continue to figure out the query range of `b`; when `b` is also an equivalent query, continue to figure out the query range of `c`. +- Otherwise, if `a` is a non-equivalent query, you can only figure out the range of `a`. + +## Operator overview + +Different operators output different information after the `EXPLAIN` statement is executed. This section focuses on the execution plan of different operators, ranging from table scans, table aggregation, to table join. + +You can use optimizer hints to control the behavior of the optimizer, and thereby controlling the selection of the physical operators. For example, `/*+ HASH_JOIN(t1, t2) */` means that the optimizer uses the `Hash Join` algorithm. For more details, see [Optimizer Hints](/optimizer-hints.md). + +### Read the execution plan of table scans -## `EXPLAIN ANALYZE` output format +The operators that perform table scans (of the disk or the TiKV Block Cache) are listed as follows: -As an extension to `EXPLAIN`, `EXPLAIN ANALYZE` will execute the query and provide additional execution statistics in the `execution info` column as follows: +- **TableFullScan**: Full table scan. +- **TableRangeScan**: Table scans with the specified range. +- **TableRowIDScan**: Accurately scans the table data based on the RowID passed down from the upper layer. +- **IndexFullScan**: Another type of "full table scan", except that the index data is scanned, rather than the table data. +- **IndexRangeScan**: Index scans with the specified range. -* `time` shows the total wall time from entering the operator until exiting the execution. It includes all execution time of any child operator operations. If the operator is called multiple times (`loops`) from a parent operator, the time will be the cumulative time. +TiDB aggregates the data or calculation results scanned from TiKV/TiFlash. The data aggregation operators can be divided into the following categories: -* `loops` is the number of times the operator was called from the parent operator. +- **TableReader**: Aggregates the data obtained by the underlying operators like `TableFullScan` or `TableRangeScan` in TiKV. +- **IndexReader**: Aggregates the data obtained by the underlying operators like `IndexFullScan` or `IndexRangeScan` in TiKV. +- **IndexLookUp**: First aggregates the RowID (in TiKV) scanned by the `Build` side. Then at the `Probe` side, accurately reads the data from TiKV based on these RowIDs. At the `Build` side, there are operators like `IndexFullScan` or `IndexRangeScan`; at the `Probe` side, there is the `TableRowIDScan` operator. +- **IndexMerge**: Similar to `IndexLookUp`. `IndexMerge` can be seen as an extension of `IndexLookupReader`. `IndexMerge` supports reading multiple indexes at the same time. There are many `Build`s and one `Probe`. The execution process of `IndexMerge` the same as that of `IndexLookUp`. -* `rows` is the total number of rows that were returned by this operator. So for example, you can compare the accuracy of the `count` column to `rows` in the `execution_info` column to assess how accurate the query optimizer's estimations are. +#### Table data and index data -* `memory` is the memory information used by this operator. Only root tasks have such information; otherwise `memeory` is `N/A`. +Table data refers to the original data of a table stored in TiKV. For each row of the table data, its `key` is a 64-bit integer called `RowID`. If a primary key of a table is an integer, TiDB uses the value of the primary key as the `RowID` of the table data; otherwise, the system automatically generates `RowID`. The `value` of the table data is encoded from all the data in this row. When reading table data, data is returned in the order of increasing `RowID`s. -* `disk` is the disk information used by this operator. Only root tasks have such information; otherwise `disk` is `N/A`. +Similar to the table data, the index data is also stored in TiKV. Its `key` is the ordered bytes encoded from the index column. `value` is the `RowID` corresponding to a row of index data. The non-index column of the row can be read using `RowID`. When reading index data, TiKV returns data in ascending order of index columns. If there are multiple index columns, firstly, ensure that the first column is incremented; if the i-th column is equivalent, make sure that the (i+1)-th column is incremented. -### Example usage +#### `IndexLookUp` example + +{{< copyable "sql" >}} ```sql -mysql> EXPLAIN ANALYZE SELECT count(*) FROM trips WHERE start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'; -+------------------------+---------+------+--------------------------------------------------------------------------------------------------+-----------------------------------+ -| id | count | task | operator info | execution info | -+------------------------+---------+------+--------------------------------------------------------------------------------------------------+-----------------------------------+ -| StreamAgg_25 | 1.00 | root | funcs:count(col_0) | time:79.851424ms, loops:2, rows:1 | -| └─IndexReader_26 | 1.00 | root | index:StreamAgg_9 | time:79.835575ms, loops:2, rows:1 | -| └─StreamAgg_9 | 1.00 | cop | funcs:count(1) | | -| └─IndexScan_24 | 8161.83 | cop | table:trips, index:start_date, range:[2017-07-01 00:00:00,2017-07-01 23:59:59], keep order:false | | -+------------------------+---------+------+--------------------------------------------------------------------------------------------------+-----------------------------------+ -4 rows in set (0.08 sec) +explain select * from t use index(idx_a); ``` -## `EXPLAIN FOR CONNECTION` +```sql ++-------------------------------+----------+-----------+-------------------------+--------------------------------+ +| id | estRows | task | access object | operator info | ++-------------------------------+----------+-----------+-------------------------+--------------------------------+ +| IndexLookUp_6 | 10000.00 | root | | | +| ├─IndexFullScan_4(Build) | 10000.00 | cop[tikv] | table:t, index:idx_a(a) | keep order:false, stats:pseudo | +| └─TableRowIDScan_5(Probe) | 10000.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++-------------------------------+----------+-----------+-------------------------+--------------------------------+ +3 rows in set (0.00 sec) +``` -`EXPLAIN FOR CONNECTION` provides the last query execution plan used by a given connection. The output format is totally the same as `EXPLAIN` in TiDB. The usage has the following semantic differences from MySQL: +The `IndexLookUp_6` operator has two child nodes: `IndexFullScan_4(Build)` and `TableRowIDScan_5(Probe)`. -- MySQL returns the plan for the currently **executing query** while TiDB returns the execution plan for the **last executed** query. -- In MySQL, executing `EXPLAIN FOR CONNECTION` for connections owned by other users requires the `PROCESS` privilege. In TiDB, this statement requires the `SUPER` privilege. ++ `IndexFullScan_4(Build)` performs an index full scan and scans all the data of index `a`. Because it is a full scan, this operation gets the `RowID` of all the data in the table. ++ `TableRowIDScan_5(Probe)` scans all table data using `RowID`s. -## Overview +This execution plan is not as efficient as using `TableReader` to perform a full table scan, because `IndexLookUp` performs an extra index scan (which comes with additional overhead), apart from the table scan. -### Introduction to task +#### `TableReader` example -Currently, there are two types of task execution: cop tasks and root tasks. A cop task refers to a computing task that is executed using the TiKV coprocessor. A root task refers to a computing task that is executed in TiDB. +{{< copyable "sql" >}} -One of the goals of SQL optimization is to push the calculation down to TiKV as much as possible. The TiKV coprocessor is able to assist in execution of SQL functions (both aggregate and scalar), SQL `LIMIT` operations, index scans, and table scans. All join operations, however, will be performed as root tasks. +```sql +explain select * from t where a > 1 or b >100; +``` -### Table data and index data +```sql ++-------------------------+----------+-----------+---------------+----------------------------------------+ +| id | estRows | task | access object | operator info | ++-------------------------+----------+-----------+---------------+----------------------------------------+ +| TableReader_7 | 8000.00 | root | | data:Selection_6 | +| └─Selection_6 | 8000.00 | cop[tikv] | | or(gt(test.t.a, 1), gt(test.t.b, 100)) | +| └─TableFullScan_5 | 10000.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++-------------------------+----------+-----------+---------------+----------------------------------------+ +3 rows in set (0.00 sec) +``` -The table data in TiDB refers to the raw data of a table, which is stored in TiKV. For each row of the table data, its key is a 64-bit integer called Handle ID. If a table has int type primary key, the value of the primary key is taken as the Handle ID of the table data, otherwise the system automatically generates the Handle ID. The value of the table data is encoded by all the data in this row. When the table data is read, return the results in the order in which the Handle ID is incremented. +In the above example, the child node of the `TableReader_7` operator is `Selection_6`. The subtree rooted at this child node is seen as a `Cop Task` and is delivered to the corresponding TiKV. This `Cop Task` uses the `TableFullScan_5` operator to perform the table scan. `Selection` represents the selection condition in the SQL statement, namely, the `WHERE`/`HAVING`/`ON` clause. -Similar to the table data, the index data in TiDB is also stored in TiKV. The key of index data is ordered bytes encoded by index columns. The value is the Handle ID of each row of index data. You can use the Handle ID to read the non-index columns in this row. When the index data is read, return the results in the order in which the index columns are incremented. If the case of multiple index columns, make sure that the first column is incremented and that the i + 1 column is incremented when the i column is equal. +`TableFullScan_5` performs a full table scan, and the load on the cluster increases accordingly, which might affect other queries running in the cluster. If an appropriate index is built and the `IndexMerge` operator is used, these will greatly improve query performance and reduce load on the cluster. -### Range query +#### `IndexMerge` example -In the WHERE/HAVING/ON condition, analyze the results returned by primary key or index key queries. For example, number and date types of comparison symbols, greater than, less than, equal to, greater than or equal to, less than or equal to, and character type LIKE symbols. +{{< copyable "sql" >}} -TiDB only supports the comparison symbols of which one side is a column and the other side is a constant or can be calculated as a constant. Query conditions like `year(birth_day) < 1992` cannot use the index. Try to use the same type to compare: additional cast operations prevent the index from being used. For example, in `user_id = 123456`, if the `user_id` is a string, you need to write `123456` as a string constant. +```sql +set @@tidb_enable_index_merge = 1; +explain select * from t use index(idx_a, idx_b) where a > 1 or b > 1; +``` + +```sql ++------------------------------+---------+-----------+-------------------------+------------------------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------------+---------+-----------+-------------------------+------------------------------------------------+ +| IndexMerge_16 | 6666.67 | root | | | +| ├─IndexRangeScan_13(Build) | 3333.33 | cop[tikv] | table:t, index:idx_a(a) | range:(1,+inf], keep order:false, stats:pseudo | +| ├─IndexRangeScan_14(Build) | 3333.33 | cop[tikv] | table:t, index:idx_b(b) | range:(1,+inf], keep order:false, stats:pseudo | +| └─TableRowIDScan_15(Probe) | 6666.67 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++------------------------------+---------+-----------+-------------------------+------------------------------------------------+ +4 rows in set (0.00 sec) +``` + +`IndexMerge` makes it possible that multiple indexes are used during table scans. In the above example, the `IndexMerge_16` operator has three child nodes, among which `IndexRangeScan_13` and `IndexRangeScan_14` get all the `RowID`s that meet the conditions based on the result of range scan, and then the `TableRowIDScan_15` operator accurately reads all the data that meet the conditions according to these `RowID`s. + +> **Note:** +> +> At present, the `IndexMerge` feature is disabled by default in TiDB 4.0.0-rc.1. In addition, the currently supported scenarios of `IndexMerge` in TiDB 4.0 are limited to the disjunctive normal form (expressions connected by `or`). The conjunctive normal form (expressions connected by `and`) will be supported in later versions. +> +> You can enable `IndexMerge` by configuring the `session` or `global` variables: execute the `set @@tidb_enable_index_merge = 1;` statement in the client. + +### Read the aggregated execution plan -Using `AND` and `OR` combination on the range query conditions of the same column is equivalent to getting the intersection or union set. For multidimensional combined indexes, you can write the conditions for multiple columns. For example, in the `(a, b, c)` combined index, when `a` is an equivalent query, you can continue to calculate the query range of `b`; when `b` is also an equivalent query, you can continue to calculate the query range of `c`; otherwise, if `a` is a non-equivalent query, you can only calculate the query range of `a`. +Aggregation algorithms in TiDB include the following categories: -## Operator info +- [Hash Aggregate](#hash-aggregate-example) +- [Stream Aggregate](#stream-aggregate-example) -### TableReader and TableScan +#### `Hash Aggregate` example -TableScan refers to scanning the table data at the KV side. TableReader refers to reading the table data from TiKV at the TiDB side. TableReader and TableScan are the two operators of one function. The `table` represents the table name in SQL statements. If the table is renamed, it displays the new name. The `range` represents the range of scanned data. If the WHERE/HAVING/ON condition is not specified in the query, full table scan is executed. If the range query condition is specified on the int type primary keys, range query is executed. The `keep order` indicates whether the table scan is returned in order. +The `Hash Aggregation` operator is optimized in multi-threaded concurrency. It is quick to execute at the cost of more memory usage. The following is an example of `Hash Aggregate`: -### IndexReader and IndexLookUp +{{< copyable "sql" >}} -The index data in TiDB is read in two ways: 1) IndexReader represents reading the index columns directly from the index, which is used when only index related columns or primary keys are quoted in SQL statements; 2) IndexLookUp represents filtering part of the data from the index, returning only the Handle ID, and retrieving the table data again using Handle ID. In the second way, data is retrieved twice from TiKV. The way of reading index data is automatically selected by the optimizer. +```sql +explain select /*+ HASH_AGG() */ count(*) from t; +``` -Similar to TableScan, IndexScan is the operator to read index data in the KV side. The `table` represents the table name in SQL statements. If the table is renamed, it displays the new name. The `index` represents the index name. The `range` represents the range of scanned data. The `out of order` indicates whether the index scan is returned in order. In TiDB, the primary key composed of multiple columns or non-int columns is treated as the unique index. +```sql ++---------------------------+----------+-----------+---------------+---------------------------------+ +| id | estRows | task | access object | operator info | ++---------------------------+----------+-----------+---------------+---------------------------------+ +| HashAgg_11 | 1.00 | root | | funcs:count(Column#7)->Column#4 | +| └─TableReader_12 | 1.00 | root | | data:HashAgg_5 | +| └─HashAgg_5 | 1.00 | cop[tikv] | | funcs:count(1)->Column#7 | +| └─TableFullScan_8 | 10000.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++---------------------------+----------+-----------+---------------+---------------------------------+ +4 rows in set (0.00 sec) +``` -### Selection +Generally speaking, `Hash Aggregate` is executed in two stages. -Selection represents the selection conditions in SQL statements, usually used in WHERE/HAVING/ON clause. +- One is on the Coprocessor of TiKV/TiFlash, with the intermediate results of the aggregation function calculated when the table scan operator reads the data. +- The other is at the TiDB layer, with the final result calculated through aggregating the intermediate results of all Coprocessor Tasks. -### Projection +#### `Stream Aggregate` example -Projection corresponds to the `SELECT` list in SQL statements, used to map the input data into new output data. +The `Stream Aggregation` operator usually takes up less memory than `Hash Aggregate`. In some scenarios, `Stream Aggregation` executes faster than `Hash Aggregate`. In the case of a large amount of data or insufficient system memory, it is recommended to use the `Stream Aggregate` operator. An example is as follows: -### Aggregation +{{< copyable "sql" >}} -Aggregation corresponds to `Group By` in SQL statements, or the aggregate functions if the `Group By` statement does not exist, such as the `COUNT` or `SUM` function. TiDB supports two aggregation algorithms: Hash Aggregation and Stream Aggregation. Hash Aggregation is a hash-based aggregation algorithm. If Hash Aggregation is close to the read operator of Table or Index, the aggregation operator pre-aggregates in TiKV to improve the concurrency and reduce the network load. +```sql +explain select /*+ STREAM_AGG() */ count(*) from t; +``` -### Join +```sql ++----------------------------+----------+-----------+---------------+---------------------------------+ +| id | estRows | task | access object | operator info | ++----------------------------+----------+-----------+---------------+---------------------------------+ +| StreamAgg_16 | 1.00 | root | | funcs:count(Column#7)->Column#4 | +| └─TableReader_17 | 1.00 | root | | data:StreamAgg_8 | +| └─StreamAgg_8 | 1.00 | cop[tikv] | | funcs:count(1)->Column#7 | +| └─TableFullScan_13 | 10000.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++----------------------------+----------+-----------+---------------+---------------------------------+ +4 rows in set (0.00 sec) +``` + +Similar to `Hash Aggregate`, `Stream Aggregate` is executed in two stages. + +- One is on the Coprocessor of TiKV/TiFlash, with the intermediate results of the aggregation function calculated when the table scan operator reads the data. +- The other is at the TiDB layer, with the final result calculated through aggregating the intermediate results of all Coprocessor Tasks. + +### Read the `Join` execution plan + +The `Join` algorithms in TiDB consist of the following categories: + +- [Hash Join](#hash-join-example) +- [Merge Join](#merge-join-example) +- [Index Join (Index Nested Loop Join)](#index-join-example) +- [Index Hash Join (Index Nested Loop Hash Join)](#index-hash-join-example) +- [Index Merge Join (Index Nested Loop Merge Join)](#index-merge-join-example) + +The following are examples of the execution processes of these `Join` algorithms. + +#### `Hash Join` example + +The `Hash Join` operator uses multi-thread. Its execution speed is fast at the cost of more memory usage. An example of `Hash Join` is as follows: + +{{< copyable "sql" >}} + +```sql +EXPLAIN SELECT /*+ HASH_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id; +``` + +```sql ++-----------------------------+----------+-----------+------------------------+------------------------------------------------+ +| id | estRows | task | access object | operator info | ++-----------------------------+----------+-----------+------------------------+------------------------------------------------+ +| HashJoin_30 | 12487.50 | root | | inner join, equal:[eq(test.t1.id, test.t2.id)] | +| ├─IndexReader_35(Build) | 9990.00 | root | | index:IndexFullScan_34 | +| │ └─IndexFullScan_34 | 9990.00 | cop[tikv] | table:t2, index:id(id) | keep order:false, stats:pseudo | +| └─IndexReader_33(Probe) | 9990.00 | root | | index:IndexFullScan_32 | +| └─IndexFullScan_32 | 9990.00 | cop[tikv] | table:t1, index:id(id) | keep order:false, stats:pseudo | ++-----------------------------+----------+-----------+------------------------+------------------------------------------------+ +5 rows in set (0.01 sec) +``` -TiDB supports Inner Join and Left/Right Outer Join, and automatically converts the external connection that can be simplified to Inner Join. +The execution process of `Hash Join` is as follows: -TiDB supports three Join algorithms: Hash Join, Sort Merge Join and Index Look up Join. The principle of Hash Join is to pre-load the memory with small tables involved in the connection and read all the data of big tables to connect. The principle of Sort Merge Join is to read the data of two tables at the same time and compare one by one using the order information of the input data. Index Look Up Join reads data of external tables and executes primary key or index key queries on internal tables. +1. Cache the data of the `Build` side in memory. +2. Construct a Hash Table on the `Build` side based on the cached data. +3. Read the data at the `Probe` side. +4. Use the data of the `Probe` side to probe the Hash Table. +5. Return qualified data to the user. + +#### `Merge Join` example + +The `Merge Join` operator usually uses less memory than `Hash Join`. However, `Merge Join` might take longer to be executed. When the amount of data is large, or the system memory is insufficient, it is recommended to use `Merge Join`. The following is an example: + +{{< copyable "sql" >}} + +```sql +EXPLAIN SELECT /*+ MERGE_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id; +``` + +```sql ++-----------------------------+----------+-----------+------------------------+-------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++-----------------------------+----------+-----------+------------------------+-------------------------------------------------------+ +| MergeJoin_7 | 12487.50 | root | | inner join, left key:test.t1.id, right key:test.t2.id | +| ├─IndexReader_12(Build) | 9990.00 | root | | index:IndexFullScan_11 | +| │ └─IndexFullScan_11 | 9990.00 | cop[tikv] | table:t2, index:id(id) | keep order:true, stats:pseudo | +| └─IndexReader_10(Probe) | 9990.00 | root | | index:IndexFullScan_9 | +| └─IndexFullScan_9 | 9990.00 | cop[tikv] | table:t1, index:id(id) | keep order:true, stats:pseudo | ++-----------------------------+----------+-----------+------------------------+-------------------------------------------------------+ +5 rows in set (0.01 sec) +``` + +The execution of the `Merge Join` operator is as follows: + +1. Read all the data of a Join Group from the `Build` side into the memory +2. Read the data of the `Probe` side. +3. Compare whether each row of data on the `Probe` side matches a complete Join Group on the `Build` side. Apart from equivalent conditions, there are non-equivalent conditions. Here "match" mainly refers to checking whether non-equivalent conditions are met. Join Group refers to the data with the same value among all Join Keys. + +#### `Index Join` example + +If the result set (obtained after the outer tables are filtered by the `WHERE` condition) is small, it is recommended to use `Index Join`. Here "small" means data is less than 10,000 rows. + +{{< copyable "sql" >}} + +```sql +EXPLAIN SELECT /*+ INL_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id; +``` + +```sql ++-----------------------------+----------+-----------+------------------------+--------------------------------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++-----------------------------+----------+-----------+------------------------+--------------------------------------------------------------------------------+ +| IndexJoin_11 | 12487.50 | root | | inner join, inner:IndexReader_10, outer key:test.t1.id, inner key:test.t2.id | +| ├─IndexReader_31(Build) | 9990.00 | root | | index:IndexFullScan_30 | +| │ └─IndexFullScan_30 | 9990.00 | cop[tikv] | table:t1, index:id(id) | keep order:false, stats:pseudo | +| └─IndexReader_10(Probe) | 1.00 | root | | index:Selection_9 | +| └─Selection_9 | 1.00 | cop[tikv] | | not(isnull(test.t2.id)) | +| └─IndexRangeScan_8 | 1.00 | cop[tikv] | table:t2, index:id(id) | range: decided by [eq(test.t2.id, test.t1.id)], keep order:false, stats:pseudo | ++-----------------------------+----------+-----------+------------------------+--------------------------------------------------------------------------------+ +6 rows in set (0.00 sec) +``` + +#### `Index Hash Join` example + +`Index Hash Join` uses the same conditions as `Index Join`. However, `Index Hash Join` saves more memory in some scenarios. + +{{< copyable "sql" >}} + +```sql +EXPLAIN SELECT /*+ INL_HASH_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id; +``` + +```sql ++-----------------------------+----------+-----------+------------------------+--------------------------------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++-----------------------------+----------+-----------+------------------------+--------------------------------------------------------------------------------+ +| IndexHashJoin_18 | 12487.50 | root | | inner join, inner:IndexReader_10, outer key:test.t1.id, inner key:test.t2.id | +| ├─IndexReader_31(Build) | 9990.00 | root | | index:IndexFullScan_30 | +| │ └─IndexFullScan_30 | 9990.00 | cop[tikv] | table:t1, index:id(id) | keep order:false, stats:pseudo | +| └─IndexReader_10(Probe) | 1.00 | root | | index:Selection_9 | +| └─Selection_9 | 1.00 | cop[tikv] | | not(isnull(test.t2.id)) | +| └─IndexRangeScan_8 | 1.00 | cop[tikv] | table:t2, index:id(id) | range: decided by [eq(test.t2.id, test.t1.id)], keep order:false, stats:pseudo | ++-----------------------------+----------+-----------+------------------------+--------------------------------------------------------------------------------+ +6 rows in set (0.00 sec) +``` + +#### `Index Merge Join` example + +`Index Merge Join` is used in similar scenarios as Index Join. However, the index prefix used by the inner table is the inner table column collection in the join keys. `Index Merge Join` saves more memory than `INL_JOIN`. + +{{< copyable "sql" >}} + +```sql +EXPLAIN SELECT /*+ INL_MERGE_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id; +``` + +```sql ++-----------------------------+----------+-----------+------------------------+-------------------------------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++-----------------------------+----------+-----------+------------------------+-------------------------------------------------------------------------------+ +| IndexMergeJoin_16 | 12487.50 | root | | inner join, inner:IndexReader_14, outer key:test.t1.id, inner key:test.t2.id | +| ├─IndexReader_31(Build) | 9990.00 | root | | index:IndexFullScan_30 | +| │ └─IndexFullScan_30 | 9990.00 | cop[tikv] | table:t1, index:id(id) | keep order:false, stats:pseudo | +| └─IndexReader_14(Probe) | 1.00 | root | | index:Selection_13 | +| └─Selection_13 | 1.00 | cop[tikv] | | not(isnull(test.t2.id)) | +| └─IndexRangeScan_12 | 1.00 | cop[tikv] | table:t2, index:id(id) | range: decided by [eq(test.t2.id, test.t1.id)], keep order:true, stats:pseudo | ++-----------------------------+----------+-----------+------------------------+-------------------------------------------------------------------------------+ +6 rows in set (0.00 sec) +``` + +## Optimization example + +For more details, refer to [bikeshare example database](/import-example-data.md). + +{{< copyable "sql" >}} + +```sql +EXPLAIN SELECT count(*) FROM trips WHERE start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'; +``` + +```sql ++------------------------------+----------+-----------+---------------+------------------------------------------------------------------------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------------+----------+-----------+---------------+------------------------------------------------------------------------------------------------------------------------+ +| StreamAgg_20 | 1.00 | root | | funcs:count(Column#13)->Column#11 | +| └─TableReader_21 | 1.00 | root | | data:StreamAgg_9 | +| └─StreamAgg_9 | 1.00 | cop[tikv] | | funcs:count(1)->Column#13 | +| └─Selection_19 | 8166.73 | cop[tikv] | | ge(bikeshare.trips.start_date, 2017-07-01 00:00:00.000000), le(bikeshare.trips.start_date, 2017-07-01 23:59:59.000000) | +| └─TableFullScan_18 | 19117643.00 | cop[tikv] | table:trips | keep order:false, stats:pseudo | ++------------------------------+----------+-----------+---------------+------------------------------------------------------------------------------------------------------------------------+ +``` + +The execution process of the above example can be illustrated as follows: + +1. Coprocessor reads the data on the trips table (executed by `TableScan_18`). +2. Find data that meets the `start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'` condition (executed by `Selection_19`). +3. Calculate the number of rows that satisfy the condition, and return the result to TiDB (executed by `StreamAgg_9`). +4. TiDB aggregates the results returned by each Coprocessor (executed by `TableReader_21`). +5. TiDB calculates the number of rows of all data (`StreamAgg_20`), and finally returns the results to the client. + +In the above query, TiDB estimates the number of rows in the output of `TableScan_18` as 19117643.00, based on the statistics of the `trips` table. The number of rows that meet the `start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'` condition is 8168.73. After the aggregation operation, there is only 1 result. + +The execution as illustrated in the above example is not efficient enough, though most of the calculation logic is pushed down to the TiKV Coprocessor. You can add an appropriate index to eliminate the full table scan on `trips` by `TableScan_18`, thereby accelerating the execution of the query: + +{{< copyable "sql" >}} + +```sql +ALTER TABLE trips ADD INDEX (start_date); +``` + +{{< copyable "sql" >}} + +```sql +EXPLAIN SELECT count(*) FROM trips WHERE start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'; +``` + +```sql ++-----------------------------+---------+-----------+-------------------------------------------+---------------------------------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++-----------------------------+---------+-----------+-------------------------------------------+---------------------------------------------------------------------------------+ +| StreamAgg_17 | 1.00 | root | | funcs:count(Column#13)->Column#11 | +| └─IndexReader_18 | 1.00 | root | | index:StreamAgg_9 | +| └─StreamAgg_9 | 1.00 | cop[tikv] | | funcs:count(1)->Column#13 | +| └─IndexRangeScan_16 | 8166.73 | cop[tikv] | table:trips, index:start_date(start_date) | range:[2017-07-01 00:00:00,2017-07-01 23:59:59], keep order:false, stats:pseudo | ++-----------------------------+---------+-----------+-------------------------------------------+---------------------------------------------------------------------------------+ +4 rows in set (0.00 sec) +``` -### Apply +After adding the index, use `IndexScan_24` to directly read the data that meets the `start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'` condition. The estimated number of rows to be scanned decreases from 19117643.00 to 8166.73. In the test environment, the execution time of this query decreases from 50.41 seconds to 0.01 seconds. -Apply is an operator used to describe subqueries in TiDB. The behavior of Apply is similar to Nested Loop. The Apply operator retrieves one piece of data from external tables, puts it into the associated column of the internal tables, executes and calculates the connection according to the inline Join algorithm in Apply. +## See also -Generally, the Apply operator is automatically converted to a Join operation by the query optimizer. Therefore, try to avoid using the Apply operator when you write SQL statements. +* [EXPLAIN](/sql-statements/sql-statement-explain.md) +* [EXPLAIN ANALYZE](/sql-statements/sql-statement-explain-analyze.md) +* [ANALYZE TABLE](/sql-statements/sql-statement-analyze-table.md) +* [TRACE](/sql-statements/sql-statement-trace.md) diff --git a/sql-statements/sql-statement-add-index.md b/sql-statements/sql-statement-add-index.md index 63d82df54d489..ddf180d1e7c8f 100644 --- a/sql-statements/sql-statement-add-index.md +++ b/sql-statements/sql-statement-add-index.md @@ -77,25 +77,25 @@ Query OK, 5 rows affected (0.03 sec) Records: 5 Duplicates: 0 Warnings: 0 mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+---------------------+----------+------+-------------------------------------------------------------+ -| id | count | task | operator info | -+---------------------+----------+------+-------------------------------------------------------------+ -| TableReader_7 | 10.00 | root | data:Selection_6 | -| └─Selection_6 | 10.00 | cop | eq(test.t1.c1, 3) | -| └─TableScan_5 | 10000.00 | cop | table:t1, range:[-inf,+inf], keep order:false, stats:pseudo | -+---------------------+----------+------+-------------------------------------------------------------+ ++-------------------------+----------+-----------+---------------+--------------------------------+ +| id | estRows | task | access object | operator info | ++-------------------------+----------+-----------+---------------+--------------------------------+ +| TableReader_7 | 10.00 | root | | data:Selection_6 | +| └─Selection_6 | 10.00 | cop[tikv] | | eq(test.t1.c1, 3) | +| └─TableFullScan_5 | 10000.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | ++-------------------------+----------+-----------+---------------+--------------------------------+ 3 rows in set (0.00 sec) mysql> ALTER TABLE t1 ADD INDEX (c1); Query OK, 0 rows affected (0.30 sec) mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+-------------------+-------+------+-----------------------------------------------------------------+ -| id | count | task | operator info | -+-------------------+-------+------+-----------------------------------------------------------------+ -| IndexReader_6 | 10.00 | root | index:IndexScan_5 | -| └─IndexScan_5 | 10.00 | cop | table:t1, index:c1, range:[3,3], keep order:false, stats:pseudo | -+-------------------+-------+------+-----------------------------------------------------------------+ ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| IndexReader_6 | 0.01 | root | | index:IndexRangeScan_5 | +| └─IndexRangeScan_5 | 0.01 | cop[tikv] | table:t1, index:c1(c1) | range:[3,3], keep order:false, stats:pseudo | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ 2 rows in set (0.00 sec) ``` diff --git a/sql-statements/sql-statement-alter-table.md b/sql-statements/sql-statement-alter-table.md index a779f959bd43d..ebc153c7ec1bc 100644 --- a/sql-statements/sql-statement-alter-table.md +++ b/sql-statements/sql-statement-alter-table.md @@ -36,25 +36,25 @@ Query OK, 5 rows affected (0.03 sec) Records: 5 Duplicates: 0 Warnings: 0 mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+---------------------+----------+------+-------------------------------------------------------------+ -| id | count | task | operator info | -+---------------------+----------+------+-------------------------------------------------------------+ -| TableReader_7 | 10.00 | root | data:Selection_6 | -| └─Selection_6 | 10.00 | cop | eq(test.t1.c1, 3) | -| └─TableScan_5 | 10000.00 | cop | table:t1, range:[-inf,+inf], keep order:false, stats:pseudo | -+---------------------+----------+------+-------------------------------------------------------------+ ++-------------------------+----------+-----------+---------------+--------------------------------+ +| id | estRows | task | access object | operator info | ++-------------------------+----------+-----------+---------------+--------------------------------+ +| TableReader_7 | 10.00 | root | | data:Selection_6 | +| └─Selection_6 | 10.00 | cop[tikv] | | eq(test.t1.c1, 3) | +| └─TableFullScan_5 | 10000.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | ++-------------------------+----------+-----------+---------------+--------------------------------+ 3 rows in set (0.00 sec) mysql> ALTER TABLE t1 ADD INDEX (c1); Query OK, 0 rows affected (0.30 sec) mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+-------------------+-------+------+-----------------------------------------------------------------+ -| id | count | task | operator info | -+-------------------+-------+------+-----------------------------------------------------------------+ -| IndexReader_6 | 10.00 | root | index:IndexScan_5 | -| └─IndexScan_5 | 10.00 | cop | table:t1, index:c1, range:[3,3], keep order:false, stats:pseudo | -+-------------------+-------+------+-----------------------------------------------------------------+ ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| IndexReader_6 | 10.00 | root | | index:IndexRangeScan_5 | +| └─IndexRangeScan_5 | 10.00 | cop[tikv] | table:t1, index:c1(c1) | range:[3,3], keep order:false, stats:pseudo | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ 2 rows in set (0.00 sec) ``` diff --git a/sql-statements/sql-statement-analyze-table.md b/sql-statements/sql-statement-analyze-table.md index 46f3f080c5628..452b37f05a4b2 100644 --- a/sql-statements/sql-statement-analyze-table.md +++ b/sql-statements/sql-statement-analyze-table.md @@ -40,24 +40,24 @@ mysql> ALTER TABLE t1 ADD INDEX (c1); Query OK, 0 rows affected (0.30 sec) mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+-------------------+-------+------+-----------------------------------------------------------------+ -| id | count | task | operator info | -+-------------------+-------+------+-----------------------------------------------------------------+ -| IndexReader_6 | 10.00 | root | index:IndexScan_5 | -| └─IndexScan_5 | 10.00 | cop | table:t1, index:c1, range:[3,3], keep order:false, stats:pseudo | -+-------------------+-------+------+-----------------------------------------------------------------+ ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| IndexReader_6 | 10.00 | root | | index:IndexRangeScan_5 | +| └─IndexRangeScan_5 | 10.00 | cop[tikv] | table:t1, index:c1(c1) | range:[3,3], keep order:false, stats:pseudo | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ 2 rows in set (0.00 sec) mysql> analyze table t1; Query OK, 0 rows affected (0.13 sec) mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+-------------------+-------+------+---------------------------------------------------+ -| id | count | task | operator info | -+-------------------+-------+------+---------------------------------------------------+ -| IndexReader_6 | 1.00 | root | index:IndexScan_5 | -| └─IndexScan_5 | 1.00 | cop | table:t1, index:c1, range:[3,3], keep order:false | -+-------------------+-------+------+---------------------------------------------------+ ++------------------------+---------+-----------+------------------------+-------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------+---------+-----------+------------------------+-------------------------------+ +| IndexReader_6 | 1.00 | root | | index:IndexRangeScan_5 | +| └─IndexRangeScan_5 | 1.00 | cop[tikv] | table:t1, index:c1(c1) | range:[3,3], keep order:false | ++------------------------+---------+-----------+------------------------+-------------------------------+ 2 rows in set (0.00 sec) ``` diff --git a/sql-statements/sql-statement-create-index.md b/sql-statements/sql-statement-create-index.md index 338d74ed3c5aa..3f4567146679f 100644 --- a/sql-statements/sql-statement-create-index.md +++ b/sql-statements/sql-statement-create-index.md @@ -81,25 +81,25 @@ Query OK, 5 rows affected (0.02 sec) Records: 5 Duplicates: 0 Warnings: 0 mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+---------------------+----------+------+-------------------------------------------------------------+ -| id | count | task | operator info | -+---------------------+----------+------+-------------------------------------------------------------+ -| TableReader_7 | 10.00 | root | data:Selection_6 | -| └─Selection_6 | 10.00 | cop | eq(test.t1.c1, 3) | -| └─TableScan_5 | 10000.00 | cop | table:t1, range:[-inf,+inf], keep order:false, stats:pseudo | -+---------------------+----------+------+-------------------------------------------------------------+ ++-------------------------+----------+-----------+---------------+--------------------------------+ +| id | estRows | task | access object | operator info | ++-------------------------+----------+-----------+---------------+--------------------------------+ +| TableReader_7 | 10.00 | root | | data:Selection_6 | +| └─Selection_6 | 10.00 | cop[tikv] | | eq(test.t1.c1, 3) | +| └─TableFullScan_5 | 10000.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | ++-------------------------+----------+-----------+---------------+--------------------------------+ 3 rows in set (0.00 sec) mysql> CREATE INDEX c1 ON t1 (c1); Query OK, 0 rows affected (0.30 sec) mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+-------------------+-------+------+-----------------------------------------------------------------+ -| id | count | task | operator info | -+-------------------+-------+------+-----------------------------------------------------------------+ -| IndexReader_6 | 10.00 | root | index:IndexScan_5 | -| └─IndexScan_5 | 10.00 | cop | table:t1, index:c1, range:[3,3], keep order:false, stats:pseudo | -+-------------------+-------+------+-----------------------------------------------------------------+ ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| IndexReader_6 | 10.00 | root | | index:IndexRangeScan_5 | +| └─IndexRangeScan_5 | 10.00 | cop[tikv] | table:t1, index:c1(c1) | range:[3,3], keep order:false, stats:pseudo | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ 2 rows in set (0.00 sec) mysql> ALTER TABLE t1 DROP INDEX c1; diff --git a/sql-statements/sql-statement-drop-index.md b/sql-statements/sql-statement-drop-index.md index 0f40d9ad822a5..615b1c00c8eee 100644 --- a/sql-statements/sql-statement-drop-index.md +++ b/sql-statements/sql-statement-drop-index.md @@ -45,25 +45,25 @@ Query OK, 5 rows affected (0.02 sec) Records: 5 Duplicates: 0 Warnings: 0 mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+---------------------+----------+------+-------------------------------------------------------------+ -| id | count | task | operator info | -+---------------------+----------+------+-------------------------------------------------------------+ -| TableReader_7 | 10.00 | root | data:Selection_6 | -| └─Selection_6 | 10.00 | cop | eq(test.t1.c1, 3) | -| └─TableScan_5 | 10000.00 | cop | table:t1, range:[-inf,+inf], keep order:false, stats:pseudo | -+---------------------+----------+------+-------------------------------------------------------------+ ++-------------------------+----------+-----------+---------------+--------------------------------+ +| id | estRows | task | access object | operator info | ++-------------------------+----------+-----------+---------------+--------------------------------+ +| TableReader_7 | 10.00 | root | | data:Selection_6 | +| └─Selection_6 | 10.00 | cop[tikv] | | eq(test.t1.c1, 3) | +| └─TableFullScan_5 | 10000.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | ++-------------------------+----------+-----------+---------------+--------------------------------+ 3 rows in set (0.00 sec) mysql> CREATE INDEX c1 ON t1 (c1); Query OK, 0 rows affected (0.30 sec) mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 3; -+-------------------+-------+------+-----------------------------------------------------------------+ -| id | count | task | operator info | -+-------------------+-------+------+-----------------------------------------------------------------+ -| IndexReader_6 | 10.00 | root | index:IndexScan_5 | -| └─IndexScan_5 | 10.00 | cop | table:t1, index:c1, range:[3,3], keep order:false, stats:pseudo | -+-------------------+-------+------+-----------------------------------------------------------------+ ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ +| IndexReader_6 | 0.01 | root | | index:IndexRangeScan_5 | +| └─IndexRangeScan_5 | 0.01 | cop[tikv] | table:t1, index:c1(c1) | range:[3,3], keep order:false, stats:pseudo | ++------------------------+---------+-----------+------------------------+---------------------------------------------+ 2 rows in set (0.00 sec) mysql> ALTER TABLE t1 DROP INDEX c1; diff --git a/sql-statements/sql-statement-explain-analyze.md b/sql-statements/sql-statement-explain-analyze.md index fe13dfc4602b1..5e46fb3680f4b 100644 --- a/sql-statements/sql-statement-explain-analyze.md +++ b/sql-statements/sql-statement-explain-analyze.md @@ -22,6 +22,17 @@ The `EXPLAIN ANALYZE` statement works similar to `EXPLAIN`, with the major diffe ![ExplainableStmt](/media/sqlgram/ExplainableStmt.png) +## EXPLAIN ANALYZE output format + +Different from `EXPLAIN`, `EXPLAIN ANALYZE` executes the corresponding SQL statement, records its runtime information, and returns the information together with the execution plan. Therefore, you can regard `EXPLAIN ANALYZE` as an extension of the `EXPLAIN` statement. Compared to `EXPLAIN`, the return results of `EXPLAIN ANALYZE` include columns of information such as `actRows`, `execution info`, `memory`, and `disk`. The details of these columns are shown as follows: + +| attribute name | description | +|:----------------|:---------------------------------| +| actRows | Number of rows output by the operator. | +| execution info | Execution information of the operator. `time` represents the total `wall time` from entering the operator to leaving the operator, including the total execution time of all sub-operators. If the operator is called many times by the parent operator (in loops), then the time refers to the accumulated time. `loops` is the number of times the current operator is called by the parent operator. | +| memory | Memory space occupied by the operator. | +| disk | Disk space occupied by the operator. | + ## Examples {{< copyable "sql" >}} @@ -30,49 +41,49 @@ The `EXPLAIN ANALYZE` statement works similar to `EXPLAIN`, with the major diffe CREATE TABLE t1 (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, c1 INT NOT NULL); ``` -``` +```sql Query OK, 0 rows affected (0.12 sec) ``` {{< copyable "sql" >}} -``` +```sql INSERT INTO t1 (c1) VALUES (1), (2), (3); ``` -``` +```sql Query OK, 3 rows affected (0.02 sec) Records: 3 Duplicates: 0 Warnings: 0 ``` {{< copyable "sql" >}} -``` -mysql> EXPLAIN ANALYZE SELECT * FROM t1 WHERE id = 1; +```sql +EXPLAIN ANALYZE SELECT * FROM t1 WHERE id = 1; ``` -``` -+-------------+-------+------+--------------------+---------------------------+ -| id | count | task | operator info | execution info | -+-------------+-------+------+--------------------+---------------------------+ -| Point_Get_1 | 1.00 | root | table:t1, handle:1 | time:0ns, loops:0, rows:0 | -+-------------+-------+------+--------------------+---------------------------+ +```sql ++-------------+---------+---------+------+---------------+--------------------------+---------------+--------+------+ +| id | estRows | actRows | task | access object | execution info | operator info | memory | disk | ++-------------+---------+---------+------+---------------+--------------------------+---------------+--------+------+ +| Point_Get_1 | 1.00 | 1 | root | table:t1 | time:177.183µs, loops:2 | handle:1 | N/A | N/A | ++-------------+---------+---------+------+---------------+--------------------------+---------------+--------+------+ 1 row in set (0.01 sec) ``` {{< copyable "sql" >}} -``` -mysql> EXPLAIN ANALYZE SELECT * FROM t1; +```sql +EXPLAIN ANALYZE SELECT * FROM t1; ``` -``` -+-----------------------+----------+-----------+------------------------------------------+-------------------------------------------------------------------------------+-----------+------+ -| id | count | task | operator info | execution info | memory | disk | -+-----------------------+----------+-----------+------------------------------------------+-------------------------------------------------------------------------------+-----------+------+ -| TableReader_5 | 10000.00 | root | data:TableFullScan_4 | time:148.128µs, loops:2, rows:3, rpc num: 1, rpc time:97.812µs, proc keys:0 | 199 Bytes | N/A | -| └─TableFullScan_4 | 10000.00 | cop[tikv] | table:t1, keep order:false, stats:pseudo | time:40.918µs, loops:4, rows:3 | N/A | N/A | -+-----------------------+----------+-----------+------------------------------------------+-------------------------------------------------------------------------------+-----------+------+ +```sql ++-----------------------+----------+---------+-----------+---------------+------------------------------------------------------------------------+--------------------------------+-----------+------+ +| id | estRows | actRows | task | access object | execution info | operator info | memory | disk | ++-----------------------+----------+---------+-----------+---------------+------------------------------------------------------------------------+--------------------------------+-----------+------+ +| TableReader_5 | 10000.00 | 3 | root | | time:454.744µs, loops:2, rpc num: 1, rpc time:328.334µs, proc keys:0 | data:TableFullScan_4 | 199 Bytes | N/A | +| └─TableFullScan_4 | 10000.00 | 3 | cop[tikv] | table:t1 | time:148.227µs, loops:4 | keep order:false, stats:pseudo | N/A | N/A | ++-----------------------+----------+---------+-----------+---------------+------------------------------------------------------------------------+--------------------------------+-----------+------+ 2 rows in set (0.00 sec) ``` diff --git a/sql-statements/sql-statement-explain.md b/sql-statements/sql-statement-explain.md index 21f8595234892..0f140d4680c27 100644 --- a/sql-statements/sql-statement-explain.md +++ b/sql-statements/sql-statement-explain.md @@ -4,12 +4,14 @@ summary: An overview of the usage of EXPLAIN for the TiDB database. aliases: ['/docs/stable/sql-statements/sql-statement-explain/','/docs/v4.0/sql-statements/sql-statement-explain/','/docs/stable/reference/sql/statements/explain/'] --- -# EXPLAIN +# `EXPLAIN` The `EXPLAIN` statement shows the execution plan for a query without executing it. It is complimented by `EXPLAIN ANALYZE` which will execute the query. If the output of `EXPLAIN` does not match the expected result, consider executing `ANALYZE TABLE` on each table in the query. The statements `DESC` and `DESCRIBE` are aliases of this statement. The alternative usage of `EXPLAIN ` is documented under [`SHOW [FULL] COLUMNS FROM`](/sql-statements/sql-statement-show-columns-from.md). +TiDB supports the `EXPLAIN [options] FOR CONNECTION connection_id` statement. However, this statement is different from the `EXPLAIN FOR` statement in MySQL. For more details, see [`EXPLAIN FOR CONNECTION`](#explain-for-connection). + ## Synopsis **ExplainSym:** @@ -24,111 +26,196 @@ The statements `DESC` and `DESCRIBE` are aliases of this statement. The alternat ![ExplainableStmt](/media/sqlgram/ExplainableStmt.png) +## `EXPLAIN` output format + +Currently, `EXPLAIN` in TiDB outputs 5 columns: `id`, `estRows`, `task`, `access object`, `operator info`. Each operator in the execution plan is described by these attributes, with each row in the `EXPLAIN` output describing an operator. The description of each attribute is as follows: + +| Attribute name | Description | +|:----------------|:----------------------------------------------------------------------------------------------------------| +| id | The operator ID is the unique identifier of the operator in the entire execution plan. In TiDB 2.1, the ID is formatted to display the tree structure of the operator. Data flows from the child node to the parent node. One and only one parent node for each operator. | +| estRows | The number of rows that the operator is expected to output. This number is estimated according to the statistics and the operator's logic. `estRows` is called `count` in the earlier versions of TiDB 4.0. | +| task | The type of task the operator belongs to. Currently, the execution plans are divided into two tasks: **root** task, which is executed on tidb-server, and **cop** task, which is performed in parallel on TiKV or TiFlash. The topology of the execution plan at the task level is that a root task followed by many cop tasks. The root task uses the output of cop tasks as input. The cop tasks refer to tasks that TiDB pushes down to TiKV or TiFlash. Each cop task is distributed in the TiKV cluster or the TiFlash cluster, and is executed by multiple processes. | +| access object | Data item information accessed by the operator. The information includes `table`, `partition`, and `index` (if any). Only operators that directly access the data have such information. | +| operator info | Other information about the operator. `operator info` of each operator is different. You can refer to the following examples. | + ## Examples +{{< copyable "sql" >}} + ```sql -mysql> EXPLAIN SELECT 1; -+-------------------+-------+------+---------------+ -| id | count | task | operator info | -+-------------------+-------+------+---------------+ -| Projection_3 | 1.00 | root | 1 | -| └─TableDual_4 | 1.00 | root | rows:1 | -+-------------------+-------+------+---------------+ +EXPLAIN SELECT 1; +``` + +```sql ++-------------------+---------+------+---------------+---------------+ +| id | estRows | task | access object | operator info | ++-------------------+---------+------+---------------+---------------+ +| Projection_3 | 1.00 | root | | 1->Column#1 | +| └─TableDual_4 | 1.00 | root | | rows:1 | ++-------------------+---------+------+---------------+---------------+ 2 rows in set (0.00 sec) +``` -mysql> CREATE TABLE t1 (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, c1 INT NOT NULL); +{{< copyable "sql" >}} + +```sql +CREATE TABLE t1 (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, c1 INT NOT NULL); +``` + +```sql Query OK, 0 rows affected (0.10 sec) +``` -mysql> INSERT INTO t1 (c1) VALUES (1), (2), (3); +{{< copyable "sql" >}} + +```sql +INSERT INTO t1 (c1) VALUES (1), (2), (3); +``` + +```sql Query OK, 3 rows affected (0.02 sec) Records: 3 Duplicates: 0 Warnings: 0 +``` + +{{< copyable "sql" >}} + +```sql +EXPLAIN SELECT * FROM t1 WHERE id = 1; +``` -mysql> EXPLAIN SELECT * FROM t1 WHERE id = 1; -+-------------+-------+------+--------------------+ -| id | count | task | operator info | -+-------------+-------+------+--------------------+ -| Point_Get_1 | 1.00 | root | table:t1, handle:1 | -+-------------+-------+------+--------------------+ +```sql ++-------------+---------+------+---------------+---------------+ +| id | estRows | task | access object | operator info | ++-------------+---------+------+---------------+---------------+ +| Point_Get_1 | 1.00 | root | table:t1 | handle:1 | ++-------------+---------+------+---------------+---------------+ 1 row in set (0.00 sec) +``` + +{{< copyable "sql" >}} + +```sql +DESC SELECT * FROM t1 WHERE id = 1; +``` -mysql> DESC SELECT * FROM t1 WHERE id = 1; -+-------------+-------+------+--------------------+ -| id | count | task | operator info | -+-------------+-------+------+--------------------+ -| Point_Get_1 | 1.00 | root | table:t1, handle:1 | -+-------------+-------+------+--------------------+ +```sql ++-------------+---------+------+---------------+---------------+ +| id | estRows | task | access object | operator info | ++-------------+---------+------+---------------+---------------+ +| Point_Get_1 | 1.00 | root | table:t1 | handle:1 | ++-------------+---------+------+---------------+---------------+ 1 row in set (0.00 sec) +``` -mysql> DESCRIBE SELECT * FROM t1 WHERE id = 1; -+-------------+-------+------+--------------------+ -| id | count | task | operator info | -+-------------+-------+------+--------------------+ -| Point_Get_1 | 1.00 | root | table:t1, handle:1 | -+-------------+-------+------+--------------------+ +{{< copyable "sql" >}} + +```sql +DESCRIBE SELECT * FROM t1 WHERE id = 1; +``` + +```sql ++-------------+---------+------+---------------+---------------+ +| id | estRows | task | access object | operator info | ++-------------+---------+------+---------------+---------------+ +| Point_Get_1 | 1.00 | root | table:t1 | handle:1 | ++-------------+---------+------+---------------+---------------+ +1 row in set (0.00 sec) +``` + +{{< copyable "sql" >}} + +```sql +EXPLAIN INSERT INTO t1 (c1) VALUES (4); +``` + +```sql ++----------+---------+------+---------------+---------------+ +| id | estRows | task | access object | operator info | ++----------+---------+------+---------------+---------------+ +| Insert_1 | N/A | root | | N/A | ++----------+---------+------+---------------+---------------+ 1 row in set (0.00 sec) +``` + +{{< copyable "sql" >}} + +```sql +EXPLAIN UPDATE t1 SET c1=5 WHERE c1=3; +``` + +```sql ++---------------------------+---------+-----------+---------------+--------------------------------+ +| id | estRows | task | access object | operator info | ++---------------------------+---------+-----------+---------------+--------------------------------+ +| Update_4 | N/A | root | | N/A | +| └─TableReader_8 | 0.00 | root | | data:Selection_7 | +| └─Selection_7 | 0.00 | cop[tikv] | | eq(test.t1.c1, 3) | +| └─TableFullScan_6 | 3.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | ++---------------------------+---------+-----------+---------------+--------------------------------+ +4 rows in set (0.00 sec) +``` + +{{< copyable "sql" >}} + +```sql +EXPLAIN DELETE FROM t1 WHERE c1=3; +``` -mysql> EXPLAIN INSERT INTO t1 (c1) VALUES (4); -ERROR 1105 (HY000): Unsupported type *core.Insert - -mysql> EXPLAIN UPDATE t1 SET c1=5 WHERE c1=3; -+---------------------+----------+------+-------------------------------------------------------------+ -| id | count | task | operator info | -+---------------------+----------+------+-------------------------------------------------------------+ -| TableReader_6 | 10.00 | root | data:Selection_5 | -| └─Selection_5 | 10.00 | cop | eq(test.t1.c1, 3) | -| └─TableScan_4 | 10000.00 | cop | table:t1, range:[-inf,+inf], keep order:false, stats:pseudo | -+---------------------+----------+------+-------------------------------------------------------------+ -3 rows in set (0.00 sec) - -mysql> EXPLAIN DELETE FROM t1 WHERE c1=3; -+---------------------+----------+------+-------------------------------------------------------------+ -| id | count | task | operator info | -+---------------------+----------+------+-------------------------------------------------------------+ -| TableReader_6 | 10.00 | root | data:Selection_5 | -| └─Selection_5 | 10.00 | cop | eq(test.t1.c1, 3) | -| └─TableScan_4 | 10000.00 | cop | table:t1, range:[-inf,+inf], keep order:false, stats:pseudo | -+---------------------+----------+------+-------------------------------------------------------------+ -3 rows in set (0.00 sec) +```sql ++---------------------------+---------+-----------+---------------+--------------------------------+ +| id | estRows | task | access object | operator info | ++---------------------------+---------+-----------+---------------+--------------------------------+ +| Delete_4 | N/A | root | | N/A | +| └─TableReader_8 | 0.00 | root | | data:Selection_7 | +| └─Selection_7 | 0.00 | cop[tikv] | | eq(test.t1.c1, 3) | +| └─TableFullScan_6 | 3.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | ++---------------------------+---------+-----------+---------------+--------------------------------+ +4 rows in set (0.01 sec) ``` If you do not specify the `FORMAT`, or specify `FORMAT = "row"`, `EXPLAIN` statement will output the results in a tabular format. See [Understand the Query Execution Plan](/query-execution-plan.md) for more information. In addition to the MySQL standard result format, TiDB also supports DotGraph and you need to specify `FORMAT = "dot"` as in the following example: +{{< copyable "sql" >}} + ```sql create table t(a bigint, b bigint); desc format = "dot" select A.a, B.b from t A join t B on A.a > B.b where A.a < 10; +``` -TiDB > desc format = "dot" select A.a, B.b from t A join t B on A.a > B.b where A.a < 10;desc format = "dot" select A.a, B.b from t A join t B on A.a > B.b where A.a < 10; -+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| dot contents | -+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +```sql ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| dot contents | ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | -digraph HashRightJoin_7 { -subgraph cluster7{ +digraph Projection_8 { +subgraph cluster8{ node [style=filled, color=lightgrey] color=black label = "root" -"HashRightJoin_7" -> "TableReader_10" -"HashRightJoin_7" -> "TableReader_12" +"Projection_8" -> "HashJoin_9" +"HashJoin_9" -> "TableReader_13" +"HashJoin_9" -> "Selection_14" +"Selection_14" -> "TableReader_17" } -subgraph cluster9{ +subgraph cluster12{ node [style=filled, color=lightgrey] color=black label = "cop" -"Selection_9" -> "TableScan_8" +"Selection_12" -> "TableFullScan_11" } -subgraph cluster11{ +subgraph cluster16{ node [style=filled, color=lightgrey] color=black label = "cop" -"TableScan_11" +"Selection_16" -> "TableFullScan_15" } -"TableReader_10" -> "Selection_9" -"TableReader_12" -> "TableScan_11" +"TableReader_13" -> "Selection_12" +"TableReader_17" -> "Selection_16" } | -+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) ``` @@ -150,6 +237,13 @@ If the `dot` program is not installed on your computer, copy the result to [this * TiDB does not support the `EXPLAIN FORMAT=JSON` as in MySQL. * TiDB does not currently support `EXPLAIN` for insert statements. +## `EXPLAIN FOR CONNECTION` + +`EXPLAIN FOR CONNECTION` is used to get the execution plan of the last executed query in a connection. The output format is the same as that of `EXPLAIN`. However, the implementation of `EXPLAIN FOR CONNECTION` in TiDB is different from that in MySQL. Their differences (apart from the output format) are listed as follows: + +- MySQL returns the query plan that is **being executing**, while TiDB returns the **last executed** query plan. +- MySQL requires the login user to be the same as the connection being queried, or the login user has the **`PROCESS`** privilege; while TiDB requires the login user to be the same as the connection being queried, or the login user has the **`SUPER`** privilege. + ## See also * [Understanding the Query Execution Plan](/query-execution-plan.md)