From ad76521c45721bd20d3c987957de26d5426d7768 Mon Sep 17 00:00:00 2001 From: Jian Zhang Date: Mon, 22 Oct 2018 16:42:35 +0800 Subject: [PATCH 1/3] add example according to the english version of the document --- sql/understanding-the-query-execution-plan.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/sql/understanding-the-query-execution-plan.md b/sql/understanding-the-query-execution-plan.md index 92cb6d190275..30ee63721ce6 100644 --- a/sql/understanding-the-query-execution-plan.md +++ b/sql/understanding-the-query-execution-plan.md @@ -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)。 | +### 用例 + +使用 [bikeshare example database](../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 简介 From 9dec33d830ad06dcfeb24ee0cf32fc051c2dfabc Mon Sep 17 00:00:00 2001 From: Jian Zhang Date: Wed, 21 Nov 2018 14:32:51 +0800 Subject: [PATCH 2/3] address comment --- sql/understanding-the-query-execution-plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/understanding-the-query-execution-plan.md b/sql/understanding-the-query-execution-plan.md index 30ee63721ce6..8fb65e952629 100644 --- a/sql/understanding-the-query-execution-plan.md +++ b/sql/understanding-the-query-execution-plan.md @@ -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); @@ -30,7 +30,7 @@ TiDB 优化器会根据当前数据表的实际情况来选择最优的执行计 ### 用例 -使用 [bikeshare example database](../bikeshare-example-database.md): +使用 [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'; From a7009dffe918dc34ee0e30be4c839566c56d0f97 Mon Sep 17 00:00:00 2001 From: Jian Zhang Date: Wed, 21 Nov 2018 15:58:44 +0800 Subject: [PATCH 3/3] address comment --- sql/understanding-the-query-execution-plan.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sql/understanding-the-query-execution-plan.md b/sql/understanding-the-query-execution-plan.md index 8fb65e952629..2690af6e42e6 100644 --- a/sql/understanding-the-query-execution-plan.md +++ b/sql/understanding-the-query-execution-plan.md @@ -46,9 +46,9 @@ mysql> EXPLAIN SELECT count(*) FROM trips WHERE start_date BETWEEN '2017-07-01 0 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 条结果 +在上面的例子中,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` 的全表扫,进一步加速查询的执行: +上述查询中,虽然大部分计算逻辑都下推到了 TiKV 的 coprocessor 上,但是其执行效率还是不够高,可以添加适当的索引来消除 `TableScan_18` 对 `trips` 的全表扫,进一步加速查询的执行: ```sql mysql> ALTER TABLE trips ADD INDEX (start_date); @@ -65,7 +65,7 @@ mysql> EXPLAIN SELECT count(*) FROM trips WHERE start_date BETWEEN '2017-07-01 0 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 秒! +在添加完索引后的新执行计划中,使用 `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 秒! ## 概述 @@ -75,15 +75,15 @@ mysql> EXPLAIN SELECT count(*) FROM trips WHERE start_date BETWEEN '2017-07-01 0 ### 表数据和索引数据 -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