关于tidb:TiDB-查询优化及调优系列二TiDB-查询计划简介

5次阅读

共计 18102 个字符,预计需要花费 46 分钟才能阅读完成。

「TiDB 查问优化及调优」系列文章将通过一些具体的案例,向大家介绍 TiDB 查问及优化相干的原理和利用,在上一篇文章中咱们简要介绍了 TiDB 查问优化器的优化流程。

查问打算(execution plan)展示了数据库执行 SQL 语句的具体步骤,例如通过索引还是全表扫描拜访表中的数据,连贯查问的实现形式和连贯的程序等。查阅及了解 TiDB 的查问打算是查问调优的根底。本文为系列文章的第二篇,将着重介绍 TiDB 查问打算以及如何查看。

算子及 Task

在上文的 TiDB 查问优化流程简介中有提到过,TiDB 的查问打算是由一系列的执行算子形成,这些算子是为返回查问后果而执行的特定步骤,例如表扫描算子,聚合算子,Join 算子等,上面以表扫描算子为例,其它算子的具体解释能够参看下文查看执行打算的小结。

执行表扫描(读盘或者读 TiKV Block Cache)操作的算子有如下几类:

  • TableFullScan:全表扫描。
  • TableRangeScan:带有范畴的表数据扫描。
  • TableRowIDScan:依据下层传递下来的 RowID 扫描表数据。时常在索引读操作后检索符合条件的行。
  • IndexFullScan:另一种“全表扫描”,扫的是索引数据,不是表数据。

目前 TiDB 的计算工作分为两种不同的 task:cop task 和 root task。Cop task 是指应用 TiKV 中的 Coprocessor 执行的计算工作,root task 是指在 TiDB 中执行的计算工作。

SQL 优化的指标之一是将计算尽可能公开推到 TiKV 中执行。TiKV 中的 Coprocessor 能反对大部分 SQL 内建函数(包含聚合函数和标量函数)、SQL LIMIT 操作、索引扫描和表扫描。然而,所有的 Join 操作都只能作为 root task 在 TiDB 上执行。

利用 EXPLAIN 查看剖析查问打算

与其它支流商业数据库一样,TiDB 中能够通过 EXPLAIN 语句返回的后果查看某条 SQL 的执行打算。

EXPLAIN 语句

目前 TiDB 的 EXPLAIN 次要输入 5 列,别离是:idestRowstaskaccess objectoperator info。执行打算中每个算子都由这 5 列属性来形容,EXPLAIN 后果中每一行形容一个算子。每个属性的具体含意如下:

EXPLAIN ANALYZE 语句

EXPLAIN 不同,EXPLAIN ANALYZE 会执行对应的 SQL 语句,记录其运行时信息,和执行打算一并返回进去,能够视为 EXPLAIN 语句的扩大。EXPLAIN ANALYZE 语句的返回后果中减少了 actRows, execution info, memory, disk 这几列信息:

例如在下例中,优化器估算的 estRows 和理论执行中统计失去的 actRows 简直是相等的,阐明优化器估算的行数与理论行数的误差很小。同时 IndexLookUp_10 算子在理论执行过程中应用了约 9 KB 的内存,该 SQL 在执行过程中,没有触发过任何算子的落盘操作。

mysql> explain analyze select * from t where a < 10;
+-------------------------------+---------+---------+-----------+-------------------------+------------------------------------------------------------------------+-----------------------------------------------------+---------------+------+
| id                            | estRows | actRows | task      | access object           | execution info                                                         | operator info                                       | memory        | disk |
+-------------------------------+---------+---------+-----------+-------------------------+------------------------------------------------------------------------+-----------------------------------------------------+---------------+------+
| IndexLookUp_10                | 9.00    | 9       | root      |                         | time:641.245µs, loops:2, rpc num: 1, rpc time:242.648µs, proc keys:0   |                                                     | 9.23046875 KB | N/A  |
| ├─IndexRangeScan_8(Build)     | 9.00    | 9       | cop[tikv] | table:t, index:idx_a(a) | time:142.94µs, loops:10,                                               | range:[-inf,10), keep order:false                   | N/A           | N/A  |
| └─TableRowIDScan_9(Probe)     | 9.00    | 9       | cop[tikv] | table:t                 | time:141.128µs, loops:10                                               | keep order:false                                    | N/A           | N/A  |
+-------------------------------+---------+---------+-----------+-------------------------+------------------------------------------------------------------------+-----------------------------------------------------+---------------+------+
3 rows in set (0.00 sec)

查看打算中算子的执行程序

TiDB 的执行打算是一个树形构造,树中每个节点即是算子。思考到每个算子内多线程并发执行的状况,在一条 SQL 执行的过程中,如果可能有一个手术刀把这棵树切开看看,大家可能会发现所有的算子都正在耗费 CPU 和内存解决数据,从这个角度来看,算子是没有执行程序的。

然而如果从一行数据先后被哪些算子解决的角度来看,一条数据在算子上的执行是有程序的。这个程序能够通过上面这个规定简略总结进去:

Build 总是先于 Probe 执行,并且 Build 总是呈现 Probe 后面

这个准则的前半句是说:如果一个算子有多个子节点,子节点 ID 前面有 Build 关键字的算子总是先于有 Probe 关键字的算子执行。后半句是说:TiDB 在展示执行打算的时候,Build 端总是第一个呈现,接着才是 Probe 端。例如:

TiDB(root@127.0.0.1:test) > explain select * from t use index(idx_a) where a = 1;
+-------------------------------+---------+-----------+-------------------------+---------------------------------------------+
| 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)

这里 IndexLookUp_7 算子有两个孩子节点:IndexRangeScan_5(Build)TableRowIDScan_6(Probe)。能够看到,IndexRangeScan_5(Build) 是第一个呈现的,并且基于下面这条规定,要失去一条数据,须要先执行它失去一个 RowID 当前,再由 TableRowIDScan_6(Probe) 依据前者读上来的 RowID 去获取残缺的一行数据。

这种规定隐含的另一个信息是: 在同一层级的节点中,呈现在最后面的算子可能是最先被执行的,而呈现在最开端的算子可能是最初被执行的。

例如上面这个例子:

TiDB(root@127.0.0.1:test) > explain select * from t t1 use index(idx_a) join t t2 use index() where t1.a = t2.a;
+----------------------------------+----------+-----------+--------------------------+------------------------------------------------------------------+
| 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)

要实现 HashJoin_22,须要先执行 TableReader_26(Build) 再执行 IndexLookUp_29(Probe)。而在执行 IndexLookUp_29(Probe) 的时候,又须要先执行 IndexFullScan_27(Build) 再执行 TableRowIDScan_28(Probe)。所以从整条执行链路来看,TableRowIDScan_28(Probe) 是最初被唤起执行的。

查看表扫描的执行打算

在上文介绍算子和工作时曾经提到过表扫描算子,这里再略微反复介绍一下,分为执行表扫描操作的算子和对扫描数据进行汇聚和计算的算子:

执行表扫描(读盘或者读 TiKV Block Cache)操作的算子有如下几类:

  • TableFullScan:全表扫描。
  • TableRangeScan:带有范畴的表数据扫描。
  • TableRowIDScan:依据下层传递下来的 RowID 扫描表数据。时常在索引读操作后检索符合条件的行。
  • IndexFullScan:另一种“全表扫描”,扫的是索引数据,不是表数据。
  • IndexRangeScan:带有范畴的索引数据扫描操作。

TiDB 会汇聚 TiKV/TiFlash 上扫描的数据或者计算结果,这种“数据汇聚”算子目前有如下几类:

  • TableReader:将 TiKV 上底层扫表算子 TableFullScan 或 TableRangeScan 失去的数据进行汇总。
  • IndexReader:将 TiKV 上底层扫表算子 IndexFullScan 或 IndexRangeScan 失去的数据进行汇总。
  • IndexLookUp:先汇总 Build 端 TiKV 扫描上来的 RowID,再去 Probe 端上依据这些 RowID 准确地读取 TiKV 上的数据。Build 端是 IndexFullScanIndexRangeScan 类型的算子,Probe 端是 TableRowIDScan 类型的算子。
  • IndexMerge:和 IndexLookupReader 相似,能够看做是它的扩大,能够同时读取多个索引的数据,有多个 Build 端,一个 Probe 端。执行过程也很相似,先汇总所有 Build 端 TiKV 扫描上来的 RowID,再去 Probe 端上依据这些 RowID 准确地读取 TiKV 上的数据。Build 端是 IndexFullScanIndexRangeScan 类型的算子,Probe 端是 TableRowIDScan 类型的算子。

IndexLookUp 示例:

mysql> explain select * from t use index(idx_a);
+-------------------------------+----------+-----------+-------------------------+--------------------------------+
| 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)

这里 IndexLookUp_6 算子有两个孩子节点:IndexFullScan_4(Build)TableRowIDScan_5(Probe)。能够看到,IndexFullScan_4(Build) 执行索引全表扫,扫描索引 a 的所有数据,因为是全范畴扫,这个操作将取得表中所有数据的 RowID,之后再由 TableRowIDScan_5(Probe) 去依据这些 RowID 去扫描所有的表数据。能够预感的是,这个执行打算不如间接应用 TableReader 进行全表扫,因为同样都是全表扫,这里的 IndexLookUp 多扫了一次索引,带来了额定的开销。

TableReader 示例:

mysql> explain select * from t where a > 1 or b >100;
+-------------------------+----------+-----------+---------------+----------------------------------------+
| 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)

在下面例子中 TableReader_7 算子的孩子节点是 Selection_6。以这个孩子节点为根的子树被当做了一个 Cop Task 下发给了相应的 TiKV,这个 Cop Task 应用 TableFullScan_5 算子执行扫表操作。Selection 示意 SQL 语句中的抉择条件,可能来自 SQL 语句中的 WHERE/HAVING/ON 子句。由 TableFullScan_5 能够看到,这个执行打算应用了一个全表扫描的操作,集群的负载将因而而回升,可能会影响到集群中正在运行的其余查问。这时候如果可能建设适合的索引,并且应用 IndexMerge 算子,将可能极大的晋升查问的性能,升高集群的负载。

IndexMerge 示例:

留神:目前 TIDB 的 Index Merge 为试验个性在 5.3 及以前版本中默认敞开,同时 5.0 中的 Index Merge 目前反对的场景仅限于析取范式(or 连贯的表达式),对合取范式(and 连贯的表达式)将在之后的版本中反对。开启 Index Merge 个性,可通过在客户端中设置 session 或者 global 变量实现:set @@tidb_enable_index_merge = 1;

mysql> set @@tidb_enable_index_merge = 1;
mysql> explain select * from t use index(idx_a, idx_b) where a > 1 or b > 1;
+------------------------------+---------+-----------+-------------------------+------------------------------------------------+
| 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 使得数据库在扫描表数据时能够应用多个索引。这里 IndexMerge_16 算子有三个孩子节点,其中 IndexRangeScan_13IndexRangeScan_14 依据范畴扫描失去符合条件的所有 RowID,再由 TableRowIDScan_15 算子依据这些 RowID 准确的读取所有满足条件的数据。

查看聚合计算的执行打算

Hash Aggregate 示例:

TiDB 上的 Hash Aggregation 算子采纳多线程并发优化,执行速度快,但会耗费较多内存。上面是一个 Hash Aggregate 的例子:

TiDB(root@127.0.0.1:test) > explain select /*+ HASH_AGG() */ count(*) from t;
+---------------------------+----------+-----------+---------------+---------------------------------+
| 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)

一般而言 TiDB 的 Hash Aggregate 会分成两个阶段执行,一个在 TiKV/TiFlash 的 Coprocessor 上,计算聚合函数的两头后果。另一个在 TiDB 层,汇总所有 Coprocessor Task 的两头后果后,失去最终后果。

Stream Aggregate 示例:

TiDB Stream Aggregation 算子通常会比 Hash Aggregate 占用更少的内存,有些场景中也会比 Hash Aggregate 执行的更快。当数据量太大或者零碎内存不足时,能够试试 Stream Aggregate 算子。一个 Stream Aggregate 的例子如下:

TiDB(root@127.0.0.1:test) > explain select /*+ STREAM_AGG() */ count(*) from t;
+----------------------------+----------+-----------+---------------+---------------------------------+
| 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)

Hash Aggregate 相似,一般而言 TiDB 的 Stream Aggregate 也会分成两个阶段执行,一个在 TiKV/TiFlash 的 Coprocessor 上,计算聚合函数的两头后果。另一个在 TiDB 层,汇总所有 Coprocessor Task 的两头后果后,失去最终后果。

查看 Join 的执行打算

TiDB 的 Join 算法包含如下几类:

  • Hash Join
  • Merge Join
  • Index Hash Join
  • Index Merge Join

Apply

上面别离通过一些例子来解释这些 Join 算法的执行过程

Hash Join 示例:

TiDB 的 Hash Join 算子采纳了多线程优化,执行速度较快,但会耗费较多内存。一个 Hash Join 的例子如下:

mysql> explain select /*+ HASH_JOIN(t1, t2) */ * from t t1 join t2 on t1.a = t2.a;
+------------------------------+----------+-----------+---------------+-------------------------------------------------------------------+
| id                           | estRows  | task      | access object | operator info                                                     |
+------------------------------+----------+-----------+---------------+-------------------------------------------------------------------+
| HashJoin_33                  | 10000.00 | root      |               | inner join, inner:TableReader_43, equal:[eq(test.t.a, test.t2.a)] |
| ├─TableReader_43(Build)      | 10000.00 | root      |               | data:Selection_42                                                 |
| │ └─Selection_42             | 10000.00 | cop[tikv] |               | not(isnull(test.t2.a))                                            |
| │   └─TableFullScan_41       | 10000.00 | cop[tikv] | table:t2      | keep order:false                                                  |
| └─TableReader_37(Probe)      | 10000.00 | root      |               | data:Selection_36                                                 |
|   └─Selection_36             | 10000.00 | cop[tikv] |               | not(isnull(test.t.a))                                             |
|     └─TableFullScan_35       | 10000.00 | cop[tikv] | table:t1      | keep order:false                                                  |
+------------------------------+----------+-----------+---------------+-------------------------------------------------------------------+
7 rows in set (0.00 sec)

Hash Join 会将 Build 端的数据缓存在内存中,依据这些数据结构出一个 Hash Table,而后读取 Probe 端的数据,用 Probe 端的数据去探测 (Probe)Build 端结构进去的 Hash Table,将符合条件的数据返回给用户。

Merge Join 示例:
TiDB 的 Merge Join 算子相比于 Hash Join 通常会占用更少的内存,但可能执行工夫会更久。当数据量太大,或零碎内存不足时,倡议尝试应用。上面是一个 Merge Join 的例子:

mysql> explain select /*+ SM_JOIN(t1) */ * from t t1 join t t2 on t1.a = t2.a;
+------------------------------------+----------+-----------+--------------------------+---------------------------------------------------+
| id                                 | estRows  | task      | access object            | operator info                                     |
+------------------------------------+----------+-----------+--------------------------+---------------------------------------------------+
| MergeJoin_6                        | 10000.00 | root      |                          | inner join, left key:test.t.a, right key:test.t.a |
| ├─IndexLookUp_13(Build)            | 10000.00 | root      |                          |                                                   |
| │ ├─IndexFullScan_11(Build)        | 10000.00 | cop[tikv] | table:t2, index:idx_a(a) | keep order:true                                   |
| │ └─TableRowIDScan_12(Probe)       | 10000.00 | cop[tikv] | table:t2                 | keep order:false                                  |
| └─IndexLookUp_10(Probe)            | 10000.00 | root      |                          |                                                   |
|   ├─IndexFullScan_8(Build)         | 10000.00 | cop[tikv] | table:t1, index:idx_a(a) | keep order:true                                   |
|   └─TableRowIDScan_9(Probe)        | 10000.00 | cop[tikv] | table:t1                 | keep order:false                                  |
+------------------------------------+----------+-----------+--------------------------+---------------------------------------------------+
7 rows in set (0.00 sec)

Merge Join 算子在执行时,会从 Build 端把一个 Join Group 的数据全副读取到内存中,接着再去读 Probe 端的数据,用 Probe 端的每行数据去和 Build 端的残缺的一个 Join Group 顺次去看是否匹配(除了满足等值条件以外,还有其余非等值条件,这里的“匹配”次要是指查看是否满足非等值职条件)。Join Group 指的是所有 Join Key 上值雷同的数据。

Index Hash Join 示例:

INL_HASH_JOIN(t1_name [, tl_name]) 提醒优化器应用 Index Nested Loop Hash Join 算法。该算法与 Index Nested Loop Join 应用条件齐全一样,但在某些场景下会更为节俭内存资源。

mysql> explain select /*+ INL_HASH_JOIN(t1) */ * from t t1 join t t2 on t1.a = t2.a;
+----------------------------------+----------+-----------+--------------------------+--------------------------------------------------------------------------+
| id                               | estRows  | task      | access object            | operator info                                                            |
+----------------------------------+----------+-----------+--------------------------+--------------------------------------------------------------------------+
| IndexHashJoin_32                 | 10000.00 | root      |                          | inner join, inner:IndexLookUp_23, outer key:test.t.a, inner key:test.t.a |
| ├─TableReader_35(Build)          | 10000.00 | root      |                          | data:Selection_34                                                        |
| │ └─Selection_34                 | 10000.00 | cop[tikv] |                          | not(isnull(test.t.a))                                                    |
| │   └─TableFullScan_33           | 10000.00 | cop[tikv] | table:t2                 | keep order:false                                                         |
| └─IndexLookUp_23(Probe)          | 1.00     | root      |                          |                                                                          |
|   ├─Selection_22(Build)          | 1.00     | cop[tikv] |                          | not(isnull(test.t.a))                                                    |
|   │ └─IndexRangeScan_20          | 1.00     | cop[tikv] | table:t1, index:idx_a(a) | range: decided by [eq(test.t.a, test.t.a)], keep order:false             |
|   └─TableRowIDScan_21(Probe)     | 1.00     | cop[tikv] | table:t1                 | keep order:false                                                         |
+----------------------------------+----------+-----------+--------------------------+--------------------------------------------------------------------------+
8 rows in set (0.00 sec)

Index Merge Join 示例:
INL_MERGE_JOIN(t1_name [, tl_name]) 提醒优化器应用 Index Nested Loop Merge Join 算法。该算法相比于 INL_JOIN 会更节俭内存。该算法应用条件蕴含 INL_JOIN 的所有应用条件,但还须要增加一条:join keys 中的内表列汇合是内表应用的 index 的前缀,或内表应用的 indexjoin keys 中的内表列汇合的前缀。

mysql> explain select /*+ INL_MERGE_JOIN(t2@sel_2) */ * from t t1 where  t1.a  in (select t2.a from t t2 where t2.b < t1.b);
+---------------------------------+---------+-----------+--------------------------+-----------------------------------------------------------------------------------------------------------+
| id                              | estRows | task      | access object            | operator info                                                                                             |
+---------------------------------+---------+-----------+--------------------------+-----------------------------------------------------------------------------------------------------------+
| IndexMergeJoin_23               | 6.39    | root      |                          | semi join, inner:Projection_21, outer key:test.t.a, inner key:test.t.a, other cond:lt(test.t.b, test.t.b) |
| ├─TableReader_28(Build)         | 7.98    | root      |                          | data:Selection_27                                                                                         |
| │ └─Selection_27                | 7.98    | cop[tikv] |                          | not(isnull(test.t.a)), not(isnull(test.t.b))                                                              |
| │   └─TableFullScan_26          | 8.00    | cop[tikv] | table:t1                 | keep order:false, stats:pseudo                                                                            |
| └─Projection_21(Probe)          | 1.25    | root      |                          | test.t.a, test.t.b                                                                                        |
|   └─IndexLookUp_20              | 1.25    | root      |                          |                                                                                                           |
|     ├─Selection_18(Build)       | 1.25    | cop[tikv] |                          | not(isnull(test.t.a))                                                                                     |
|     │ └─IndexRangeScan_16       | 1.25    | cop[tikv] | table:t2, index:idx_a(a) | range: decided by [eq(test.t.a, test.t.a)], keep order:true, stats:pseudo                                 |
|     └─Selection_19(Probe)       | 1.25    | cop[tikv] |                          | not(isnull(test.t.b))                                                                                     |
|       └─TableRowIDScan_17       | 1.25    | cop[tikv] | table:t2                 | keep order:false, stats:pseudo                                                                            |
+---------------------------------+---------+-----------+--------------------------+-----------------------------------------------------------------------------------------------------------+
10 rows in set (0.01 sec)

Apply 示例:

mysql> explain select * from t t1 where  t1.a  in (select avg(t2.a) from t2 where t2.b < t1.b);
+----------------------------------+----------+-----------+---------------+-------------------------------------------------------------------------------+
| id                               | estRows  | task      | access object | operator info                                                                 |
+----------------------------------+----------+-----------+---------------+-------------------------------------------------------------------------------+
| Projection_10                    | 10000.00 | root      |               | test.t.id, test.t.a, test.t.b                                                 |
| └─Apply_12                       | 10000.00 | root      |               | semi join, inner:StreamAgg_30, equal:[eq(Column#8, Column#7)]                 |
|   ├─Projection_13(Build)         | 10000.00 | root      |               | test.t.id, test.t.a, test.t.b, cast(test.t.a, decimal(20,0) BINARY)->Column#8 |
|   │ └─TableReader_15             | 10000.00 | root      |               | data:TableFullScan_14                                                         |
|   │   └─TableFullScan_14         | 10000.00 | cop[tikv] | table:t1      | keep order:false                                                              |
|   └─StreamAgg_30(Probe)          | 1.00     | root      |               | funcs:avg(Column#12, Column#13)->Column#7                                     |
|     └─TableReader_31             | 1.00     | root      |               | data:StreamAgg_19                                                             |
|       └─StreamAgg_19             | 1.00     | cop[tikv] |               | funcs:count(test.t2.a)->Column#12, funcs:sum(test.t2.a)->Column#13            |
|         └─Selection_29           | 8000.00  | cop[tikv] |               | lt(test.t2.b, test.t.b)                                                       |
|           └─TableFullScan_28     | 10000.00 | cop[tikv] | table:t2      | keep order:false                                                              |
+----------------------------------+----------+-----------+-----------------------------------------------------------------------------------------------+
10 rows in set, 1 warning (0.00 sec)

其它对于 EXPLAIN 的阐明

EXPLAIN FOR CONNECTION 用于取得一个连贯中最初执行的查问的执行打算,其输入格局与 EXPLAIN 完全一致。但 TiDB 中的实现与 MySQL 不同,除了输入格局之外,还有以下区别:

MySQL 返回的是正在执行的查问打算,而 TiDB 返回的是最初执行的查问打算。

MySQL 的文档中指出,MySQL 要求登录用户与被查问的连贯雷同,或者领有 PROCESS 权限,而 TiDB 则要求登录用户与被查问的连贯雷同,或者领有 SUPER 权限。

本文为「TiDB 查问优化及调优」系列文章的第二篇,后续将持续对 TiDB 慢查问诊断监控及排查、调整及优化查问执行打算以及其余优化器开发或布局中的诊断调优性能等进行介绍。如果您对 TiDB 的产品有任何倡议,欢送来到 https://internals.tidb.io 与咱们交换。

点击查看更多 TiDB 查问优化及调优文章

正文完
 0