TiDB执行计划(二)

接一篇TiDB执行计划(一),上一篇中主要介绍了执行计划中涉及到的算子,今天把执行计划中剩余的东西讲完

查询计划命令

EXPLAIN命令,可以查看TiDB执行sql时的执行计划,用法和mysql一样,跟上sql即可

EXPLAIN  SQL语句

举个栗子(脱敏数据)

执行 EXPLAIN

EXPLAIN 
select
  a0_.id,
  a0_.create_time,
  a0_.end_time,
  a0_.flow_id,
  a0_.campaign_id,
  a0_.unit_id,
  a0_.oa_id,
  a0_.org_path_,
  a0_.param,
  a0_.start_time,
  a0_.state,
  a0_.user_type,
  a0_.update_time,
  a0_.user_id
from
  table_a a0_
where
  a0_.campaign_id = 354361236223
  and a0_.user_id = 25325123
  and a0_.user_type = 1
  and a0_.param = '1'
limit
  1000

执行计划结果

执行计划结果

执行计划以一个树形结构展示出来,来说说每一列的含义吧:

  • id 为算子,是执行sql时,每一步需要执行子任务
  • estRows为每一个子任务预估需要处理的行数
  • task为子任务执行时候所在的位置
  • access-object子任务的对象,比如说表、索引等
  • operator info子任务执行时候的一些算是操作日志的信息吧

上一篇文章说了算子,今天来说下执行计划中,剩下这几个字段estRowstaskaccess-objectoperator info的含义吧

estRows:为每一个子任务预估需要处理的行数
这个很容易理解,就直接上栗子了
select
  user_id
from
  tablea a0_
GROUP by
  user_id

这个sql,对于索引列user_id使用了group by,导致了执行时需要对所有索引数据进行扫描,会出现IndexFullScan算子,执行计划如下:

estRows
  • 因为这个sql的执行计划是,先对于索引列user_id进行了索引数据全量进行扫描,使用了IndexFullScan算子,所以IndexFullScan_11这一步的算子的预估行数estRows是索引列user_id全量数据的数据量,133270314
  • 这个sql后一步的执行是通过IndexReader算子对下层算子的数据进行一个聚合,所以IndexReader_13算子的预估行数estRowsuser_id列 group by以后的数据,也就是873229.35了
task:为子任务执行时候所在的位置
  • 为子任务执行时候所在的位置
  • 主要有两种
  • cop,是指使用TiKV中的Coprocessor执行的计算任务,支持大部分函数(包括聚合函数和标量函数)、LIMIT操作、索引扫描和表扫描
  • root,是指在TiDB中执行的计算任务,一般所有汇聚TiKV/TiFlash上扫描的数据或者计算结果的算子都只能作为roottask在TiDB上执行,所有的Join操作都只能作为roottask在TiDB上执行
  • TiDB的SQL优化的目标之一是将计算尽可能地下推到TiKV中执行
举个栗子

栗子1:聚合查询栗子,使用COUNT

select
  COUNT(user_id)
from
  tablea a0_

这个sql,对于索引列user_id使用了COUNT函数,导致了执行时需要对所有索引数据进行扫描,会出现IndexFullScan算子,执行计划如下:

COUNT函数
  • 对于索引列user_id使用了COUNT函数,先会对索引列user_id进行索引数据全量扫描,IndexFullScan_19算子的执行位置为 cop[tikv]
  • 后续执行SteamAgg_8算子时候,因为是 聚合函数,也会在 cop[tikv]上执行
  • 最终的IndexReader_21算子对下层算子的数据进行一个聚合,在root也就是TiDB中执行

栗子2:聚合查询栗子,使用group by

select
  user_id
from
  tablea a0_
GROUP by
  user_id

这个sql,对于索引列user_id使用了group by,导致了执行时需要对所有索引数据进行扫描,会出现IndexFullScan算子,执行计划如下:

group by
  • 对于索引列user_id使用了group by,先会对索引列user_id进行索引数据全量扫描,IndexFullScan_11算子的执行位置为 cop[tikv]
  • 后续执行HashAgg_5算子时候,因为是group by,也会在 cop[tikv]上执行
  • 最终的IndexReader_13算子对下层算子的数据进行一个聚合,在root也就是TiDB中执行

栗子3:子查询栗子,使用索引IN 子查询,当子查询为全量时

select
  *
from
  tablea a0_
where
  user_id IN (
    select
      user_id
    from
      tablea
  )

这个sql,对于索引列user_id使用了in,子查询为全表扫描,所以会导致外层查询会对索引列user_id进行全索引数据进行扫描,会出现IndexFullScan算子,执行计划如下:

IN 子查询
  • 首先,子查询没有加条件,是一个全表扫描,看执行计划2的地方,出现了一个TableFullScan_49,由于子查询是全量数据,会在cop[tikv]上执行
  • 聚合子查询结果的TableReader_50会在root也就是TiDB中执行
  • 看执行计划1的地方,当外层sql对索引列user_id进行In时候,会对索引列user_id进行全索引数据的扫描,IndexFullScan_40会在cop[tikv]上执行
  • 同样1位置的聚合算子IndexReader_42root也就是TiDB中执行
  • 最终的HashJoin_22算子对下层算子的数据进行一个聚合,在root也就是TiDB中执行
access-object: 子任务的对象,比如说表、索引等
这个很容易理解,就直接上栗子了
select
  * 
from
  tablea a1_
where
  a1_.user_id = 123214125

执行计划如下:

TableRowIDScan栗子
  • 看这个sql,是一个通过索引列user_id进行了索引范围扫描,他的执行逻辑是,先通过对于索引列user_id进行了一个范围扫描,得到所有符合条件的rowId,然后通过rowId扫描表获得数据,看执行也是,首先在Build端,通过IndexRangeScan算子,对于索引列user_id进行了范围扫描,扫描到的rowId,在Probe端,在通过TableRowIDScan算子,通过rowId扫描表获取数据,最终通过IndexLookUp算子来汇聚最终的数据
  • 看这个sql执行计划的access-object
  • 首先在Build端,通过IndexRangeScan_8(Build)算子,对于索引列user_id进行了范围扫描,所以该算子的对象是table:a0_,index:idx_user_id(user_id),意思为操作的对象是表a0_的索引idx_user_id(user_id)
  • 然后通过范围扫描索引得到的rowId扫描表获得数据,所以TableRowIDScan_9(Probe)算子的操作对象是表a0_
operator info: 子任务执行时候的一些算是操作日志的信息
这个很容易理解,基本是每一步的操作日志,就不举栗子说明,从原来的栗子中都可以看的懂

TiDB执行计划中的算子就为大家说到这里,欢迎大家来交流,指出文中一些说错的地方,让我加深认识。

你可能感兴趣的:(TiDB执行计划(二))