接一篇TiDB執(zhí)行計劃(一),上一篇中主要介紹了執(zhí)行計劃中涉及到的算子,今天把執(zhí)行計劃中剩余的東西講完
查詢計劃命令
EXPLAIN命令,可以查看TiDB執(zhí)行sql時的執(zhí)行計劃,用法和mysql一樣,跟上sql即可
EXPLAIN SQL語句
舉個栗子(脫敏數(shù)據(jù))
執(zhí)行 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
執(zhí)行計劃結(jié)果

執(zhí)行計劃結(jié)果
執(zhí)行計劃以一個樹形結(jié)構(gòu)展示出來,來說說每一列的含義吧:
-
id為算子,是執(zhí)行sql時,每一步需要執(zhí)行子任務 -
estRows為每一個子任務預估需要處理的行數(shù) -
task為子任務執(zhí)行時候所在的位置 -
access-object子任務的對象,比如說表、索引等 -
operator info子任務執(zhí)行時候的一些算是操作日志的信息吧
上一篇文章說了算子,今天來說下執(zhí)行計劃中,剩下這幾個字段estRows、task、access-object、operator info的含義吧
estRows:為每一個子任務預估需要處理的行數(shù)
這個很容易理解,就直接上栗子了
select
user_id
from
tablea a0_
GROUP by
user_id
這個sql,對于索引列user_id使用了group by,導致了執(zhí)行時需要對所有索引數(shù)據(jù)進行掃描,會出現(xiàn)IndexFullScan算子,執(zhí)行計劃如下:

estRows
- 因為這個sql的執(zhí)行計劃是,先對于索引列
user_id進行了索引數(shù)據(jù)全量進行掃描,使用了IndexFullScan算子,所以IndexFullScan_11這一步的算子的預估行數(shù)estRows是索引列user_id全量數(shù)據(jù)的數(shù)據(jù)量,133270314 - 這個sql后一步的執(zhí)行是通過
IndexReader算子對下層算子的數(shù)據(jù)進行一個聚合,所以IndexReader_13算子的預估行數(shù)estRows是user_id列 group by以后的數(shù)據(jù),也就是873229.35了
task:為子任務執(zhí)行時候所在的位置
- 為子任務執(zhí)行時候所在的位置
- 主要有兩種
-
cop,是指使用TiKV中的Coprocessor執(zhí)行的計算任務,支持大部分函數(shù)(包括聚合函數(shù)和標量函數(shù))、LIMIT操作、索引掃描和表掃描 -
root,是指在TiDB中執(zhí)行的計算任務,一般所有匯聚TiKV/TiFlash上掃描的數(shù)據(jù)或者計算結(jié)果的算子都只能作為roottask在TiDB上執(zhí)行,所有的Join操作都只能作為roottask在TiDB上執(zhí)行 - TiDB的SQL優(yōu)化的目標之一是將計算盡可能地下推到
TiKV中執(zhí)行
舉個栗子
栗子1:聚合查詢栗子,使用COUNT:
select
COUNT(user_id)
from
tablea a0_
這個sql,對于索引列user_id使用了COUNT函數(shù),導致了執(zhí)行時需要對所有索引數(shù)據(jù)進行掃描,會出現(xiàn)IndexFullScan算子,執(zhí)行計劃如下:

COUNT函數(shù)
- 對于索引列
user_id使用了COUNT函數(shù),先會對索引列user_id進行索引數(shù)據(jù)全量掃描,IndexFullScan_19算子的執(zhí)行位置為cop[tikv] - 后續(xù)執(zhí)行
SteamAgg_8算子時候,因為是 聚合函數(shù),也會在cop[tikv]上執(zhí)行 - 最終的
IndexReader_21算子對下層算子的數(shù)據(jù)進行一個聚合,在root也就是TiDB中執(zhí)行
栗子2:聚合查詢栗子,使用group by:
select
user_id
from
tablea a0_
GROUP by
user_id
這個sql,對于索引列user_id使用了group by,導致了執(zhí)行時需要對所有索引數(shù)據(jù)進行掃描,會出現(xiàn)IndexFullScan算子,執(zhí)行計劃如下:

group by
- 對于索引列
user_id使用了group by,先會對索引列user_id進行索引數(shù)據(jù)全量掃描,IndexFullScan_11算子的執(zhí)行位置為cop[tikv] - 后續(xù)執(zhí)行
HashAgg_5算子時候,因為是group by,也會在cop[tikv]上執(zhí)行 - 最終的
IndexReader_13算子對下層算子的數(shù)據(jù)進行一個聚合,在root也就是TiDB中執(zhí)行
栗子3:子查詢栗子,使用索引IN 子查詢,當子查詢?yōu)槿繒r:
select
*
from
tablea a0_
where
user_id IN (
select
user_id
from
tablea
)
這個sql,對于索引列user_id使用了in,子查詢?yōu)槿頀呙?,所以會導致外層查詢會對索引?code>user_id進行全索引數(shù)據(jù)進行掃描,會出現(xiàn)IndexFullScan算子,執(zhí)行計劃如下:

IN 子查詢
- 首先,子查詢沒有加條件,是一個全表掃描,看執(zhí)行計劃2的地方,出現(xiàn)了一個
TableFullScan_49,由于子查詢是全量數(shù)據(jù),會在cop[tikv]上執(zhí)行 - 聚合子查詢結(jié)果的
TableReader_50會在root也就是TiDB中執(zhí)行 - 看執(zhí)行計劃1的地方,當外層sql對索引列
user_id進行In時候,會對索引列user_id進行全索引數(shù)據(jù)的掃描,IndexFullScan_40會在cop[tikv]上執(zhí)行 - 同樣1位置的聚合算子
IndexReader_42在root也就是TiDB中執(zhí)行 - 最終的
HashJoin_22算子對下層算子的數(shù)據(jù)進行一個聚合,在root也就是TiDB中執(zhí)行
access-object: 子任務的對象,比如說表、索引等
這個很容易理解,就直接上栗子了
select
*
from
tablea a1_
where
a1_.user_id = 123214125
執(zhí)行計劃如下:

TableRowIDScan栗子
- 看這個sql,是一個通過索引列
user_id進行了索引范圍掃描,他的執(zhí)行邏輯是,先通過對于索引列user_id進行了一個范圍掃描,得到所有符合條件的rowId,然后通過rowId掃描表獲得數(shù)據(jù),看執(zhí)行也是,首先在Build端,通過IndexRangeScan算子,對于索引列user_id進行了范圍掃描,掃描到的rowId,在Probe端,在通過TableRowIDScan算子,通過rowId掃描表獲取數(shù)據(jù),最終通過IndexLookUp算子來匯聚最終的數(shù)據(jù) - 看這個sql執(zhí)行計劃的
access-object - 首先在
Build端,通過IndexRangeScan_8(Build)算子,對于索引列user_id進行了范圍掃描,所以該算子的對象是table:a0_,index:idx_user_id(user_id),意思為操作的對象是表a0_的索引idx_user_id(user_id) - 然后通過范圍掃描索引得到的
rowId掃描表獲得數(shù)據(jù),所以TableRowIDScan_9(Probe)算子的操作對象是表a0_
operator info: 子任務執(zhí)行時候的一些算是操作日志的信息
這個很容易理解,基本是每一步的操作日志,就不舉栗子說明,從原來的栗子中都可以看的懂
TiDB執(zhí)行計劃中的算子就為大家說到這里,歡迎大家來交流,指出文中一些說錯的地方,讓我加深認識。