接一篇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
子任务执行时候的一些算是操作日志的信息吧
上一篇文章说了算子,今天来说下执行计划中,剩下这几个字段estRows
、task
、access-object
、operator info
的含义吧
estRows
:为每一个子任务预估需要处理的行数
这个很容易理解,就直接上栗子了
select
user_id
from
tablea a0_
GROUP by
user_id
这个sql,对于索引列user_id
使用了group by
,导致了执行时需要对所有索引数据进行扫描,会出现IndexFullScan
算子,执行计划如下:
- 因为这个sql的执行计划是,先对于索引列
user_id
进行了索引数据全量进行扫描,使用了IndexFullScan
算子,所以IndexFullScan_11
这一步的算子的预估行数estRows
是索引列user_id
全量数据的数据量,133270314 - 这个sql后一步的执行是通过
IndexReader
算子对下层算子的数据进行一个聚合,所以IndexReader_13
算子的预估行数estRows
是user_id
列 group by以后的数据,也就是873229.35了
task
:为子任务执行时候所在的位置
- 为子任务执行时候所在的位置
- 主要有两种
-
cop
,是指使用TiKV
中的Coprocessor
执行的计算任务,支持大部分函数(包括聚合函数和标量函数)、LIMIT
操作、索引扫描和表扫描 -
root
,是指在TiDB
中执行的计算任务,一般所有汇聚TiKV/TiFlash
上扫描的数据或者计算结果的算子都只能作为root
task在TiDB
上执行,所有的Join
操作都只能作为root
task在TiDB
上执行 - TiDB的SQL优化的目标之一是将计算尽可能地下推到
TiKV
中执行
举个栗子
栗子1:聚合查询栗子,使用COUNT
:
select
COUNT(user_id)
from
tablea a0_
这个sql,对于索引列user_id
使用了COUNT函数
,导致了执行时需要对所有索引数据进行扫描,会出现IndexFullScan
算子,执行计划如下:
- 对于索引列
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
算子,执行计划如下:
- 对于索引列
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
算子,执行计划如下:
- 首先,子查询没有加条件,是一个全表扫描,看执行计划2的地方,出现了一个
TableFullScan_49
,由于子查询是全量数据,会在cop[tikv]
上执行 - 聚合子查询结果的
TableReader_50
会在root
也就是TiDB
中执行 - 看执行计划1的地方,当外层sql对索引列
user_id
进行In时候,会对索引列user_id
进行全索引数据的扫描,IndexFullScan_40
会在cop[tikv]
上执行 - 同样1位置的聚合算子
IndexReader_42
在root
也就是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执行计划中的算子就为大家说到这里,欢迎大家来交流,指出文中一些说错的地方,让我加深认识。
网友评论