Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sql: add the Bikeshare example according to English doc #937

Merged
merged 3 commits into from
Nov 21, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 45 additions & 6 deletions sql/understanding-the-query-execution-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ TiDB 优化器会根据当前数据表的实际情况来选择最优的执行计

`EXPLAIN` 语句的返回结果提供了 TiDB 执行 SQL 查询的详细信息:

- `EXPLAIN` 可以和 `SELECT`,`DELETE`,`INSERT`,`REPLACE`,以及 `UPDATE` 语句一起使用;
- `EXPLAIN` 可以和 `SELECT`,`DELETE` 语句一起使用;
- 执行 `EXPLAIN`,TiDB 会返回被 `EXPLAIN` 的 SQL 语句经过优化器后的最终物理执行计划。也就是说,`EXPLAIN` 展示了 TiDB 执行该 SQL 语句的完整信息,比如以什么样的顺序,什么方式 JOIN 两个表,表达式树长什么样等等。详见 [`EXPLAIN` 输出格式](#explain-output-format);
- TiDB 目前还不支持 `EXPLAIN [options] FOR CONNECTION connection_id`,将在未来支持它,详见 [#4351](https://github.com/pingcap/tidb/issues/4351);

Expand All @@ -28,6 +28,45 @@ TiDB 优化器会根据当前数据表的实际情况来选择最优的执行计
| task | 当前这个 operator 属于什么 task。目前的执行计划分成为两种 task,一种叫 **root** task,在 tidb-server 上执行,一种叫 **cop** task,并行的在 TiKV 上执行。当前的执行计划在 task 级别的拓扑关系是一个 root task 后面可以跟许多 cop task,root task 使用 cop task 的输出结果作为输入。cop task 中执行的也即是 TiDB 下推到 TiKV 上的任务,每个 cop task 分散在 TiKV 集群中,由多个进程共同执行。 |
| operator info | 每个 operator 的详细信息。各个 operator 的 operator info 各有不同,详见 [Operator Info](#operator-info)。 |

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment not related to this PR. In the first section of this file, we have:

EXPLAIN 可以和 SELECT,DELETE,INSERT,REPLACE,以及 UPDATE 语句一起使用;

maybe we should remove INSERT and REPLACE in this PR together.

mysql> explain insert into t1 values(1,1);
ERROR 1105 (HY000): Unsupported type *core.Insert
mysql> explain replace into t1 values(1,1);
ERROR 1105 (HY000): Unsupported type *core.Insert

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, they are bugs, I think we should fix it 😂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove them in this PR, let's add them back once the bugs are fixed.

### 用例

使用 [bikeshare example database](https://github.com/pingcap/docs/blob/master/bikeshare-example-database.md):

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

在上面的例子中,coprocessor 上读取 `trips` 表上的数据(`TableScan_18`),寻找满足 `start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'` 条件的数据(`Selection_19`),然后计算满足条件的数据行数(`StreamAgg_9`),最后把结果返回给 TiDB。TiDB 汇总各个 coprocessor 返回的结果(`TableReader_21`),并进一步计算所有数据的行数(`StreamAgg_20`),最终把结果返回给客户端。在上面这个查询中,TiDB 根据 `trips` 表的统计信息估算出 `TableScan_18` 的输出结果行数为 19117643.00,满足条件 `start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'` 的有 `8166.73` 条,经过聚合运算后,只有 1 条结果。

上述查询中,虽然大部分计算逻辑都下推到了 TiKV 的 coprocessor 上,但是其执行效率还是不够高,可以添加适当的索引来消除 `TableScan_18` 对 `trips` 的全表扫,进一步加速查询的执行:

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

在添加完索引后的新执行计划中,使用 `IndexScan_24` 直接读取满足条件 `start_date BETWEEN '2017-07-01 00:00:00' AND '2017-07-01 23:59:59'` 的数据,可以看到,估算的要扫描的数据行数从之前的 `19117643.00` 降到了现在的 `8166.73`。在测试环境中显示,这个查询的执行时间从 50.41 秒降到了 0.00 秒!

## 概述

### Task 简介
Expand All @@ -36,15 +75,15 @@ TiDB 优化器会根据当前数据表的实际情况来选择最优的执行计

### 表数据和索引数据

TiDB 的表数据是指一张表的原始数据,存放在 TiKV 中。对于每行表数据,它的 key 是一个 64 位整数,称为 Handle ID。如果一张表存在 int 类型的主键,我们会把主键的值当作表数据的 Handle ID,否则由系统自动生成 Handle ID。表数据的 value 由这一行的所有数据编码而成。在读取表数据的时候,可以按照 Handle ID 递增的顺序返回。
TiDB 的表数据是指一张表的原始数据,存放在 TiKV 中。对于每行表数据,它的 key 是一个 64 位整数,称为 Handle ID。如果一张表存在 int 类型的主键,TiDB 会把主键的值当作表数据的 Handle ID,否则由系统自动生成 Handle ID。表数据的 value 由这一行的所有数据编码而成。在读取表数据的时候,可以按照 Handle ID 递增的顺序返回。

TiDB 的索引数据和表数据一样,也存放在 TiKV 中。它的 key 是由索引列编码的有序 bytes,value 是这一行索引数据对应的 Handle ID,通过 Handle ID 我们可以读取这一行的非索引列。在读取索引数据的时候,我们按照索引列递增的顺序返回,如果有多个索引列,我们首先保证第 1 列递增,并且在第 i 列相等的情况下,保证第 i + 1 列递增。
TiDB 的索引数据和表数据一样,也存放在 TiKV 中。它的 key 是由索引列编码的有序 bytes,value 是这一行索引数据对应的 Handle ID,通过 Handle ID 可以读取这一行的非索引列。在读取索引数据的时候,TiKV 会按照索引列递增的顺序返回,如果有多个索引列,首先保证第 1 列递增,并且在第 i 列相等的情况下,保证第 i + 1 列递增。

### 范围查询

在 WHERE/HAVING/ON 条件中,我们会分析主键或索引键的查询返回。如数字、日期类型的比较符,如大于、小于、等于以及大于等于、小于等于,字符类型的 LIKE 符号等。
值得注意的是,我们只支持比较符一端是列,另一端是常量,或可以计算成某一常量的情况,类似 `year(birth_day) < 1992` 的查询条件是不能利用索引的。还要注意应尽可能使用同一类型进行比较,以避免引入额外的 cast 操作而导致不能利用索引,如 `user_id = 123456`,如果 `user_id` 是字符串,需要将 `123456` 也写成字符串常量的形式。
针对同一列的范围查询条件使用 `AND` 和 `OR` 组合后,等于对范围求交集或者并集。对于多维组合索引,我们可以写多个列的条件。例如对组合索引`(a, b, c)`,当 a 为等值查询时,可以继续求 b 的查询范围,当 b 也为等值查询时,可以继续求 c 的查询范围,反之如果 a 为非等值查询,则只能求 a 的范围。
在 WHERE/HAVING/ON 条件中,TiDB 优化器会分析主键或索引键的查询返回。如数字、日期类型的比较符,如大于、小于、等于以及大于等于、小于等于,字符类型的 LIKE 符号等。
值得注意的是,TiDB 目前只支持比较符一端是列,另一端是常量,或可以计算成某一常量的情况,类似 `year(birth_day) < 1992` 的查询条件是不能利用索引的。还要注意应尽可能使用同一类型进行比较,以避免引入额外的 cast 操作而导致不能利用索引,如 `user_id = 123456`,如果 `user_id` 是字符串,需要将 `123456` 也写成字符串常量的形式。
针对同一列的范围查询条件使用 `AND` 和 `OR` 组合后,等于对范围求交集或者并集。对于多维组合索引,可以写多个列的条件。例如对组合索引`(a, b, c)`,当 a 为等值查询时,可以继续求 b 的查询范围,当 b 也为等值查询时,可以继续求 c 的查询范围,反之如果 a 为非等值查询,则只能求 a 的范围。

## Operator Info

Expand Down