全部产品
Search
文档中心

执行计划和基本算子

更新时间: 2020-08-07

通常 SQL 调优的过程离不开以下两个步骤:

  1. 通过 EXPLAIN 指令查看执行计划,或通过 EXPLAIN ANALYZE 查看实际执行情况(参见查询执行器介绍章节),分析问题所在
  2. 尝试通过 Hint 控制优化器行为,将执行计划修改成我们期望的样子

本章中,我们将介绍如何使用 EXPLAIN 命令和阅读输出的执行计划,并介绍一些基本的算子。更多算子(例如 Join、Agg、Sort等)的介绍在后续的章节中单独列出。

执行计划与 EXPLAIN 命令

在前一章节中,我们已经看到了一个执行计划的例子。这里我们通过一个例子,讲解如何通过 EXPLAIN 指令获取查询的执行计划。

EXPLAIN 语法如下(注意 Hint 需要放在 EXPLAIN 之后):

  1. EXPLAIN <SQL Statement>
  2. EXPLAIN <Hint> <SQL Statement>

本文中的示例均基于以下表结构:

  1. CREATE TABLE `sbtest1` (
  2. `id` INT(10) UNSIGNED NOT NULL,
  3. `k` INT(10) UNSIGNED NOT NULL DEFAULT '0',
  4. `c` CHAR(120) NOT NULL DEFAULT '',
  5. `pad` CHAR(60) NOT NULL DEFAULT '',
  6. KEY `xid` (`id`),
  7. KEY `k_1` (`k`)
  8. ) dbpartition BY HASH (`id`) tbpartition BY HASH (`id`) tbpartitions 4

执行以下 EXPLAIN 指令,可以看到相应的执行计划被打印出来了:

  1. mysql> explain select a.k, count(*) cnt from sbtest1 a, sbtest1 b where a.id = b.k and a.id > 1000 group by k having cnt > 1300 order by cnt limit 5, 10;
  2. +---------------------------------------------------------------------------------------------------------------------------------------------------+
  3. | LOGICAL PLAN |
  4. +---------------------------------------------------------------------------------------------------------------------------------------------------+
  5. | MemSort(sort="cnt ASC", offset=?2, fetch=?3) |
  6. | Filter(condition="cnt > ?1") |
  7. | HashAgg(group="k", cnt="COUNT()") |
  8. | BKAJoin(id="id", k="k", c="c", pad="pad", id0="id0", k0="k0", c0="c0", pad0="pad0", condition="id = k", type="inner") |
  9. | MergeSort(sort="k ASC") |
  10. | LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?) ORDER BY `k`") |
  11. | Gather(concurrent=true) |
  12. | LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE ((`k` > ?) AND (`k` IN ('?')))") |
  13. | HitCache:false |
  14. +---------------------------------------------------------------------------------------------------------------------------------------------------+
  15. 9 rows in set (0.01 sec)

除执行计划外,EXPLAIN 结果中还会有一些额外信息,上面的例子中仅有一项 HitCache(是否命中 Plan Cache 缓存),详细原理请参见执行计划管理

算子介绍

DRDS 中支持以下算子:

含义 物理算子
下发查询 LogicalView,LogicalModifyView,PhyTableOperation
连接(Join) BKAJoin,NLJoin,HashJoin,SortMergeJoin,HashSemiJoin,SortMergeSemiJoin,MaterializedSemiJoin
排序 MemSort,TopN
聚合(Group-By) HashAgg,SortAgg
数据交换 Gather,MergeSort
其它 Project,Filter, Limit,Union,Window

以下介绍部分算子的含义和实现,剩余的部分在后面的章节中介绍。

LogicalView

LogicalView 是从MySQL数据源拉取数据的算子,类似于其他数据库中的 TableScan 或 IndexScan,但支持更多的下推。LogicalView 中包含下推的 SQL 语句和数据源信息,更像一个“视图”。其中下推的 SQL 可能包含多种算子,如 Project、Filter、聚合、排序、Join 和子查询等。

以下通过示例说明 EXPLAIN 中 LogicalView 的输出信息及其含义:

  1. > explain select * From sbtest1 where id > 1000;
  2. Gather(concurrent=true)
  3. LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?)")

LogicalView 的信息由三部分构成:

  • tables:底层数据源对应的表名,以 . 分割,. 之前是分库对应的编号,. 之后是表名及其编号,如 [000-127] 表示表名编号从 000 到 127 的所有表。
  • shardCount:需要访问的分表总数,该示例中会访问从 000 到 127 共计 128 张分表。
  • sql:下发至底层数据源的 SQL 模版,DRDS 在执行时会将表名替换为物理表名,参数化的常量(?)会被替换成实际参数(详见执行计划管理一章)。

Gather

Gather 将多份数据合并成同份数据。上面的例子中,Gather 将各个分表上查询到的数据合并成一份。

在不使用并行查询时,Gather 通常出现在 LogicalView 上方,表示收集合并各个分表的数据。如果并行查询开启,Gather 可能被上拉到更高的地方,这时候 Gather 表示将各个 Worker 的计算结果收集合并,语义是类似的。

Gather 中的 concurrent 表示是否并发执行子算子,默认为 true,表示并发拉取数据。开启并行查询时,上拉的 Gather 属性有所变化,显示为 parallel=true

MergeSort

MergeSort 即归并排序算子,表示将有序的数据流进行归并排序,合并成一个有序的数据流。例如:

  1. > explain select * from sbtest1 where id > 1000 order by id limit 5,10;
  2. MergeSort(sort="id ASC", offset=?1, fetch=?2)
  3. LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?) ORDER BY `id` LIMIT (? + ?)")

MergeSort 算子包含三部分内容:

  • sort:表示排序字段以及排列顺序,id ASC 表示按照ID字段递增排序,DESC 表示递减排序
  • offset:表示获取结果集时的偏移量,例子中被参数化了,实际值为 5
  • fetch:表示最多返回的数据行数。与 offset 类似,同样是参数化的表示,实际对应的值为 10。

Project

Project 表示投影操作,即从输入数据中选择部分列输出,或者对某些列进行转换(通过函数或者表达式计算)后输出,当然,也可以包含常量。

  1. > explain select '你好, DRDS', 1 / 2, CURTIME();
  2. Project(你好, DRDS="_UTF-16'你好, DRDS'", 1 / 2="1 / 2", CURTIME()="CURTIME()")

Project 的计划中包括每列的列名及其对应的列、值、函数或者表达式。

Filter

Filter 表示过滤操作,其中包含一些过滤条件。该算子对输入数据进行过滤,若满足条件,则输出,否则丢弃。如下是一个较复杂的例子,包含了以上介绍的大部分算子。

  1. > explain select k, avg(id) avg_id from sbtest1 where id > 1000 group by k having avg_id > 1300;
  2. Filter(condition="avg_id > ?1")
  3. Project(k="k", avg_id="sum_pushed_sum / sum_pushed_count")
  4. SortAgg(group="k", sum_pushed_sum="SUM(pushed_sum)", sum_pushed_count="SUM(pushed_count)")
  5. MergeSort(sort="k ASC")
  6. LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT `k`, SUM(`id`) AS `pushed_sum`, COUNT(`id`) AS `pushed_count` FROM `sbtest1` WHERE (`id` > ?) GROUP BY `k` ORDER BY `k`")

有读者可能会问,WHERE id > 1000 中的条件为什么没有对应的 Filter 算子呢?这个算子最终被下推到了 LogicalView 中,可以在 LogicalView 的SQL中看到 WHERE (id > ?)

UnionAll 与 UnionDistinct

顾名思义,UnionAll 对应 UNION ALL ,UnionDistinct 对应 UNION DISTINCT。该算子通常有 2 个或更多个输入,表示将多个输入的数据合并在一起。例如:

  1. > explain select * From sbtest1 where id > 1000 union distinct select * From sbtest1 where id < 200;
  2. UnionDistinct(concurrent=true)
  3. Gather(concurrent=true)
  4. LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?)")
  5. Gather(concurrent=true)
  6. LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` < ?)")

LogicalModifyView

如上文介绍,LogicalView 表示从底层数据源获取数据的算子,与之对应的,LogicalModifyView 表示对底层数据源的修改算子,其中也会记录一个 SQL 语句,该 SQL 可能是 INSERT、UPDATE 或者 DELETE。

  1. > explain update sbtest1 set c='Hello, DRDS' where id > 1000;
  2. LogicalModifyView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="UPDATE `sbtest1` SET `c` = ? WHERE (`id` > ?)"
  1. > explain delete from sbtest1 where id > 1000;
  2. LogicalModifyView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="DELETE FROM `sbtest1` WHERE (`id` > ?)")

LogicalModifyView 查询计划的内容与 LogicalView 类似,包括下发的物理分表,分表数以及 SQL 模版。同样,由于开启了 PlanCache,对 SQL 做了参数化处理,SQL 模版中的常量会用 ? 替换。

PhyTableOperation

PhyTableOperation 表示对某个物理分表直接执行一个操作,该算子目前仅用于 INSERT 语句。

  1. > explain insert into sbtest1 values(1, 1, '1', '1'),(2, 2, '2', '2');
  2. PhyTableOperation(tables="SYSBENCH_CORONADB_1526954857179TGMMSYSBENCH_CORONADB_VGOC_0000_RDS.[sbtest1_001]", sql="INSERT INTO ? (`id`, `k`, `c`, `pad`) VALUES(?, ?, ?, ?)", params="`sbtest1_001`,1,1,1,1")
  3. PhyTableOperation(tables="SYSBENCH_CORONADB_1526954857179TGMMSYSBENCH_CORONADB_VGOC_0000_RDS.[sbtest1_002]", sql="INSERT INTO ? (`id`, `k`, `c`, `pad`) VALUES(?, ?, ?, ?)", params="`sbtest1_002`,2,2,2,2")

示例中,INSERT 插入两行数据,每行数据对应一个 PhyTableOperation 算子,PhyTableOperation 算子的内容包括三部分:

  • tables:物理表名,仅有唯一一个物理表名。
  • sql:SQL 模版,该 SQL 模版中表名和常量均被参数化,用 ? 替换,对应的参数在随后的 params 中给出。
  • params:SQL 模版对应的参数,包括表名和常量。