2.17 执行计划
2.17.1 执行计划的意义
对执行计划进行分析,可以了解中间件和节点是否对SQL语句生成了最优的执行计划,是否有优化的空间,从而为SQL优化提供重要的参考信息。
2.17.2 执行计划的分类
dble的执行计划分为两个层次:dble层的执行计划与节点层的执行计划。
dble层的执行计划: 在SQL语句执行前,dble会根据SQL语句的基本信息,判断该SQL语句应该在哪些节点上执行,将SQL改写成在节点上执行的具体形式,并决定采用何种策略进行数据合并与计算等。
节点层的执行计划: 就是原生的MySQL执行计划。
2.17.3 dble层的执行计划
dble用EXPLAIN指令来查看dble层的执行计划。如例1:
explain select * from test;
+---------------+----------+------------------------------+
| SHARDING_NODE | TYPE | SQL/REF |
+---------------+----------+------------------------------+
| dn1 | BASE SQL | SELECT * FROM test LIMIT 100 |
| dn2 | BASE SQL | SELECT * FROM test LIMIT 100 |
+---------------+----------+------------------------------+
2 rows in set (0.01 sec)
或者例2:
mysql> explain select * from test where id =1;
+---------------+----------+--------------------------------+
| SHARDING_NODE | TYPE | SQL/REF |
+---------------+----------+--------------------------------+
| dn1 | BASE SQL | select * from test where id =1 |
+---------------+----------+--------------------------------+
1 row in set (0.04 sec)
EXPLAIN指令的执行结果包括语句下发的节点,实际下发的SQL语句和数据的合并操作的信息。这些信息是系统静态分析产生的,并没有真正的执行语句。
另外,复杂查询的查询计划也会有所反映,可以通过计划来优化查询语句
如例3:
mysql> explain select * from sharding_two_node a inner join sharding_four_node b on a.id =b.id;
+---------------+----------+----------------------------------------------------------------------------------------------------+
| SHARDING_NODE | TYPE | SQL/REF |
+---------------+----------+----------------------------------------------------------------------------------------------------+
| dn1.0 | BASE SQL | select `a`.`id`,`a`.`c_char`,`a`.`ts`,`a`.`si` from `sharding_two_node` `a` ORDER BY `a`.`id` ASC |
| dn2.0 | BASE SQL | select `a`.`id`,`a`.`c_char`,`a`.`ts`,`a`.`si` from `sharding_two_node` `a` ORDER BY `a`.`id` ASC |
| dn1.1 | BASE SQL | select `b`.`id`,`b`.`c_flag`,`b`.`c_decimal` from `sharding_four_node` `b` ORDER BY `b`.`id` ASC |
| dn2.1 | BASE SQL | select `b`.`id`,`b`.`c_flag`,`b`.`c_decimal` from `sharding_four_node` `b` ORDER BY `b`.`id` ASC |
| dn3.0 | BASE SQL | select `b`.`id`,`b`.`c_flag`,`b`.`c_decimal` from `sharding_four_node` `b` ORDER BY `b`.`id` ASC |
| dn4.0 | BASE SQL | select `b`.`id`,`b`.`c_flag`,`b`.`c_decimal` from `sharding_four_node` `b` ORDER BY `b`.`id` ASC |
| merge.1 | MERGE | dn1.0, dn2.0 |
| merge.2 | MERGE | dn1.1, dn2.1, dn3.0, dn4.0 |
| join.1 | JOIN | merge.1, merge.2 |
+---------------+----------+----------------------------------------------------------------------------------------------------+
9 rows in set (0.00 sec)
再举例4:
mysql> explain select id from single union all select b.si from sharding_four_node a inner join sharding_two_node b on a.id =b.id
+-----------------+-----------+------------------------------------------------------------------------------+
| SHARDING_NODE | TYPE | SQL/REF |
+-----------------+-----------+------------------------------------------------------------------------------+
| dn1.0 | BASE SQL | select `single`.`id` from `single` |
| dn1.1 | BASE SQL | select `a`.`id` from `sharding_four_node` `a` ORDER BY `a`.`id` ASC |
| dn2.0 | BASE SQL | select `a`.`id` from `sharding_four_node` `a` ORDER BY `a`.`id` ASC |
| dn3.0 | BASE SQL | select `a`.`id` from `sharding_four_node` `a` ORDER BY `a`.`id` ASC |
| dn4.0 | BASE SQL | select `a`.`id` from `sharding_four_node` `a` ORDER BY `a`.`id` ASC |
| dn1.2 | BASE SQL | select `b`.`si`,`b`.`id` from `sharding_two_node` `b` ORDER BY `b`.`id` ASC |
| dn2.1 | BASE SQL | select `b`.`si`,`b`.`id` from `sharding_two_node` `b` ORDER BY `b`.`id` ASC |
| merge.2 | MERGE | dn1.1, dn2.0, dn3.0, dn4.0 |
| merge.3 | MERGE | dn1.2, dn2.1 |
| join.1 | JOIN | merge.2, merge.3 |
| merge.1 | MERGE | dn1.0 |
| union_all.1 | UNION_ALL | join.1, merge.1 |
+-----------------+-----------+------------------------------------------------------------------------------+
12 rows in set (0.01 sec)
我们看到,查询计划分为3列,分别是SHARDING_NODE,TYPE和SQL/REF。
2.17.3.1 简单查询计划的解读
首先,我们要建立一个概念,查询计划结果分两类,一类是简单查询,如例1例2,中间件只做路由计算,所以查询计划比较简单。
这种场景下,
- SHARDING_NODE列的内容对应于sharding.xml中的shardingNode的name属性,表明sql将被发往哪里。
- TYPE列恒为
BASE SQL
,表示这是个基本查询 - SQL/REF列实际退化为SQL,标识了实际下发的SQL内容,看例1的无条件广播实际就被改写,增加了limit 100(当然这个功能也可以设置或者关闭)。
2.17.3.2 复杂查询计划的解读
另一类是复杂查询,这类查询其实需要dble进行计算,比如例3中的跨库join,实际是构建一个类似这样的查询树。
┌────────────┐
│ │
┌────────► join.1 ◄─────────┐
│ │ │ │
│ └────────────┘ │
│ │
┌─────┴─────┐ ┌─────┴─────┐
│ │ │ │
┌────────► merge.1 │ ┌──────────────► merge.2 ◄────────┐
│ │ │ │ │ │ │
│ └─────▲─────┘ │ └──▲───▲────┘ │
│ │ │ │ │ │
│ │ │ │ │ │
┌───┴───┐ ┌──────┴─┐ ┌────┴───┐ ┌────────┬┘ ┌─┴─────┐ ┌────┴──┐
│ │ │ │ │ │ │ │ │ │ │ │
│ dn1.0 │ │ dn2.0 │ │ dn1.1 │ │ dn2.1 │ │ dn3.0 │ │ dn4.0 │
└───────┘ └────────┘ └────────┘ └────────┘ └───────┘ └───────┘
查询树更多细节请参考公开课相关章节。 所以,理解了查询树,就能理解查询计划了。
- SHARDING_NODE列的内容分两类,一类对应于sharding.xml中的shardingNode的name属性,表明sql将被发往哪里,对应查询树的叶子结点,和简单查询类似,另一类是查询树的非叶子结点,代表了在dble中需要计算的算子的名称。 由于同一个结点可能有多次查询下发。同名算子也可能有多个,所以用点号和自增序列区别。
- TYPE列:叶子结点为
BASE SQL
,表示这是下发的基本查询,非叶子结点表示是算子的类型:算子包括:- MERGE:合并
- MERGE_AND_ORDER:带排序的合并(归并排序)
- AGGREGATE:聚合
- DISTINCT:去重
- LIMIT:取前n行
- WHERE_FILTER:where过滤
- HAVING_FILTER:having过滤
- SHUFFLE_FIELD:列名&数据类型整理
- UNION_ALL:union all操作,如果原sql是union,会裂为UNION_ALL和DISTINCT两个算子
- ORDER:排序
- NOT_IN:not in 处理。
- JOIN:join操作
- DIRECT_GROUP:直接group by。
- NEST_LOOP:NEST_LOOP 类型的join,结果返回处理
- IN_SUB_QUERY:in子查询结果返回处理。
- ALL_ANY_SUB_QUERY:all/any子查询结果返回处理。
- SCALAR_SUB_QUERY:标量子查询结果返回处理
- RENAME_DERIVED_SUB_QUERY:DERIVED子查询结果处理。
- INNER_FUNC_ADD: sql包含一些dble特殊处理的函数时的处理逻辑,例如LAST_INSERT_ID
- INNER_FUNC_MERGE:sql的select仅包含dble特殊处理的函数时的处理逻辑,例如LAST_INSERT_ID。
- for CHILD in UPDATE_SUB_QUERY.RESULTS:update多表场景中,遍历先下发的select语句的结果集,填充到update单表语句后下发。
- MERGE UPDATE:update多表场景中,回收整理循环下发的结果
- SQL/REF列:叶子结点退化为SQL,标识了实际下发的SQL内容;非叶子结点表达了查询树中子节点的名称,对应SHARDING_NODE列的名称。通过这样,就可以通过查询计划画出一颗完整的查询树来,也就能了解分布式查询计划的细节了。
2.17.4 节点层的执行计划
通过EXPLAIN2命令可查看指定节点上的执行计划。如:
mysql> explain2 shardingnode=dn1 sql=select * from test where id =1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
explain2会将sql语句加上explain下发到指定的shardingnode执行,并把节点上explain的结果返回客户端,可以用于观察单个节点的sql执行计划。