一、执行计划生成

        EXPLAIN和EXPLAIN ANALYZE是金仓分析型数据库系统优化性能的工具。EXPLAIN会为查询显示其查询计划和估算的代价,但是不执行该查询。EXPLAIN ANALYZE除了显示查询的查询计划之外,还会执行该查询。EXPLAIN ANALYZE会忽略任何来自SELECT语句的输出,但是该语句中的其他操作会被执行(例如INSERT、UPDATE或者DELETE)。要在 DM L 语句上使用EXPLAIN ANALYZE却不让该命令影响数据,建议把EXPLAIN ANALYZE用在一个事务中。

二、执行计划构成

        执行计划是数据库中将要执行的查询产生的一组操作树,计划中的每个节点代表数据库的一个操作,例如表扫描,连接,分组聚集,排序以及并行处理需要数据移动节点等。查询计划应该按照从下向上的顺序来读和执行。

2.1、操作节点   

类型

节点操作

含义

扫描

Seqscan

顺序扫描在数据库中,是最常见,也是最简单的一种方式,就是将一个数据文件从头到尾读取一次,这种方式非常符合磁盘的读写特性,顺序读写,吞吐很高。对于分析性的语句,顺序扫描基本上是对全表的所有数据进行分析计算,因此这一种方式非常有效。在数据仓库中,绝大部分都是这种扫描方式,在 KingbaseAnalyticsDB中结合压缩表一起使用,可以减少磁盘 IO 的损耗。

Indexscan

索引扫描是通过索引来定位数据的,一般对数据进行特定的筛选,筛选后的数据量比较小。使用索引进行筛选,必须先在筛选的字段上建立索引,查询时先通过索引文件定位到实际数据在数据文件中的位置,再返回数据。对于磁盘而言,索引扫描都是随机 IO,对于查询小数据量而言,速度很快。

Bitmapheapscan

当索引定位到的数据在整表中占比较大的时候,通过索引定位到的数据会使用位图的方式对索引字段进行位图堆表扫描,以确定结果数据的准确。对于数据仓库应用而言,很少用这种扫描方式。

Subqueryscan

子查询扫描,只要 SQL 中有子查询,需要对子查询的结果做顺序扫描,就会进行子查询扫描。

Functionscan

函数扫描,数据库中有一些函数的返回值是一个结果集,当数据库从这个结果集中取出数据的时候,就会用到这个 FunctionScan,顺序获取函数返回的结果集。

关联

Nestloop join

Nestloop join 就是所谓的笛卡尔积,这种关联的效率极差。如果对两张大表做笛卡尔积,会产生极大的中间表,随时有可能将数据库的存储耗光,导致整个数据库垮掉,后果相当严重,所以在数据库中,看到了 Nestloop,那就要小心了。

Hash join

关联时候采用的一种很高效的方法,它先对其中一张关联的表计算 hash 值,在内存中用一个散列表保存,然后对另外一张表进行全表扫描,之后将每一行与这个散列表进行关联。

Merge join

对两张表都按照关联字段进行排序,然后按照排序好的内容顺序遍历一遍,将相同的值连接起来,从而实现了连接。使用这种方法,最大的消耗是对两表进行排序。

Semi joins

多在子查询 exists 中使用,对外部行源每个键值,查找到内部行源匹配的第一个键

值后就返回,如果找到就不用再查找内部行源其他的键值了。

Anti joins

多用于 !=      not in 等查询;如果找到满足条件(!= not in)的不返回,不满足条件(!=not in)的返回。

分组和聚集

HashAggregate

对于 hash 聚合来说,数据库会根据 Group By 字段后面的值算出 hash 值,并根据前面使用的聚合函数在内存中维护对应的列表。如果 select 后面有两个聚合函数,那么在内存中就会维护两个对应的数据。Group By 后面的字段重复值越少使用的内存也就越大。

GroupAggregate

对于普通聚合函数,使用 GroupAggregate,其原理是先将表中的数据按照 Group By 的字段排序,这样同一个 Group By 的值就在一起,只需要对排好序的数据进行一次全扫描,并进行对应的聚合函数的计算,就可以得到聚合的结果了。

2.2、排序

        排序(ORDER BY) 操作的作用是根据指定属性对整个查询的结果进行排序。KingbaseAnalyticsDB排序使用了查询下推优化技术极大提升了排序性能。

2.3、数据移动

类型

节点操作

含义

数据移动

Gather Motion

聚合操作,在管理节点上将子节点所有的数据聚合起来。一般的聚合规则是:哪一个子节点的数据先返回到管理节点上就将该节点的数据放到管理实例。

Broadcast Motion

广播,将每个计算实例上某一个表的数据全部发送给其它所有计算实例。这样每一个 计算实例都相当于有一份全量数据,广播基本只会出现在两边关联的时候,根据相关内容选择广播或者重分布。

Redistribute Motion

当需要做跨库关联或者聚合的时候,当数据不能满足广播的条件,或者广播的消耗过大时,就会选择重分布数据,即数据按照新的分布键(关联键)重新打散到每个计算实例上,重分布一般在以下三种情况下会发生:关联、Group By、窗口函数。

二、执行计划查看

2.1、EXPLAIN

        EXPLAIN 命令输出查询计划,一个查询计划是一棵节点的树。计划中的每个节点表示一个操作,例如表扫描、连接、聚集或者排序。应该从底向上阅读计划:每个节点会把记录交给直接在它上面的节点。一个计划中的底层节点通常是表扫描操作:顺序的、索引的或者位图索引扫描。如果该查询要求那些行上的连接、聚集、排序或者其他操作,就会有额外的节点在扫描节点上面负责执行这些操作。最顶层的计划节点通常是 KingbaseAnalyticsDB数据库的移动节点:重新分布、广播或者收集移动。这些操作在 KingbaseAnalyticsDB实例之间移动行。

        计划树节点类型和执行代价估计:

  •  cost —以磁盘页面获取为单位度量。1.0 等于一次顺序磁盘页面读取。第一个估计是得到第一行的启动代价,第二个估计是得到所有行的总代价。总代价是假定所有的行都将被检索,但并不总是这样。例如,如果查询使用了LIMIT,并非所有的行都会被检索。
  •  rows —这个计划节点输出总行数。这个数字通常小于被该计划节点处理或者扫描的行数,它反映了任意WHERE子句条件的估计选择度。理想情况下,最顶层节点的估计近似于该查询实际返回、更新或者删除的行数。
  •  width —这个计划节点输出的所有行的总字节数。

        下面的例子描述了如何阅读一个查询的 EXPLAIN 查询代价:

EXPLAIN SELECT * FROM names WHERE name = 'Joelle';

 


QUERY PLAN

 


------------------------------------------------------------

 


Gather Motion 2:1 (slice1) (cost=0.00..20.88 rows=1 width=13)

 



-> Seq Scan on 'names' (cost=0.00..20.88 rows=1 width=13)

 



Filter: name::text ~~ 'Joelle'::text

        从底向上阅读这个计划。一开始,查询优化器顺序地扫描 names 表。注意 WHERE 子句被应用为一个 filter 条件。这意味着扫描操作会对它扫描的每个行检查该条件并且只输出满足该条件的行。扫描操作的结果被传递给一个收集操作。在 KingbaseAnalyticsDB 数据库中,收集是实例何时把记录发送给管理实例。在这个例子中,我们有两个实例会向一个管理实例发送。这个操作工作在并行执行计划的 slice1 上。查询计划会被划分成切片,这样实例可以并行工作。为这个计划估计的启动代价是 0.00(没有代价)而总代价是 20.88 次磁盘页面获取。优化器估计这个查询将返回一条记录。

2.2、EXPLAIN ANALYZE

        EXPLAIN ANALYZE 规划并且运行语句。EXPLAIN ANALYZE 计划会把实际执行代价和优化器的估计一起显示。这允许用户查看优化器的估计是否接近于实际。EXPLAIN A NALYZE也展示下列信息:

       EXPLAIN A NALYZE展示下列信息: 

  •  查询执行的总运行时间(以毫秒为单位)。
  •  查询计划每个切片使用的内存,以及为整个查询语句保留的内存。
  •  一个计划节点操作中涉及的实例数量。其中只统计返回记录的实例。
  •  为该操作产生最多记录的实例返回的最大数量。如果多个实例产生了相等的行数,EXPLAIN ANALYZE会显示那个用了最长时间的实例。
  •  相关操作使用的内存量。如果内存量不足以在执行该操作,计划会显示溢出到磁盘的数据量最少的实例的溢出数据量。
  •  产生最多记录的实例检索到第一行的时间(以毫秒为单位)以及该实例检索到所有记录所用的时间。

        下面例子用同一个查询描述了如何阅读一个 EXPLAIN ANALYZE 查询计划。这个计划展示了每一个计划节点的实际计时和返回行,以及整个查询的内存和时间统计信息。 

EXPLAIN ANALYZE SELECT * FROM names WHERE name = 'Joelle';

 


QUERY PLAN

 


------------------------------------------------------------

 


Gather Motion 2:1 (slice1; segments: 2) (cost=0.00..20.88 rows=1 width=13)

 



Rows out: 1 rows at destination with 0.305 ms to first row, 0.537 ms to end, start offset by 0.289 ms.

 



-> Seq Scan on names (cost=0.00..20.88 rows=1 width=13)

 



Rows out: Avg 1 rows x 2 workers. Max 1 rows (seg0) with 0.255 ms to first row,

 



0.486 ms to end, start offset by 0.968 ms.

 



Filter: name = 'Joelle'::text

 


Slice statistics:

 


(slice0) Executor memory: 135K bytes.

 


(slice1) Executor memory: 151K bytes avg x 2 workers, 151K bytes max (seg0).

 


Statement statistics:

 


Memory used: 128000K bytes

 


Total runtime: 22.548 ms

        从底向上阅读这个查询。运行这个查询花掉的总时间是 22.548 毫秒。seq scan 操作只有一个返回记录的实例(seg0),并且它只返回 1 行。它用了 0. 255 毫秒找到第一行且用了 0.486 毫秒来扫描所有的行。这个结果接近于优化器的估计:查询优化器估计这个查询将会返回一行。收集接收到 1 条记录。这个操作的总消耗时间是 0.537 毫秒。