这里对查询计划的学习主要是对TPC-H中Query2的分析。

1.Query的查询语句

select
s_acctbal,
s_name,
n_name,
p_partkey,
p_mfgr,
s_address,
s_phone,
s_comment
from
part,
supplier,
partsupp,
nation,
region
where
p_partkey = ps_partkey
and s_suppkey = ps_suppkey
and p_size =
and p_type like '%COPPER'
and s_nationkey = n_nationkey
and n_regionkey = r_regionkey
and r_name = 'AFRICA'
and ps_supplycost = (
select
min(ps_supplycost)
from
partsupp,
supplier,
nation,
region
where
p_partkey = ps_partkey
and s_suppkey = ps_suppkey
and s_nationkey = n_nationkey
and n_regionkey = r_regionkey
and r_name = 'AFRICA'
)
order by
s_acctbal desc,
n_name,
s_name,
p_partkey
LIMIT ;

2.查看查询计划

Greenplum中有语句可以查看查询计划,使用explain命令即可:

例:testDB=#explain select * from test1;

所以Query2的查询计划查看命令即Query2的语句之前加explain。

3.查询中涉及到的表

testdb=# \d part
Append-Only Columnar Table "public.part"
Column | Type | Modifiers
---------------+-----------------------+----------------------------------------------------------
p_partkey | bigint | not null default nextval('part_p_partkey_seq'::regclass)
p_name | character varying() |
p_mfgr | character() |
p_brand | character() |
p_type | character varying() |
p_size | integer |
p_container | character() |
p_retailprice | numeric |
p_comment | character varying() |
Checksum: t
Distributed by: (p_partkey) testdb=# \d partsupp
Append-Only Columnar Table "public.partsupp"
Column | Type | Modifiers
---------------+------------------------+-----------
ps_partkey | bigint | not null
ps_suppkey | bigint | not null
ps_availqty | integer |
ps_supplycost | numeric |
ps_comment | character varying() |
Checksum: t
Indexes:
"idx_partsupp_partkey" btree (ps_partkey)
"idx_partsupp_suppkey" btree (ps_suppkey)
Distributed by: (ps_partkey, ps_suppkey) testdb=# \d supplier
Append-Only Columnar Table "public.supplier"
Column | Type | Modifiers
-------------+------------------------+--------------------------------------------------------------
s_suppkey | bigint | not null default nextval('supplier_s_suppkey_seq'::regclass)
s_name | character() |
s_address | character varying() |
s_nationkey | bigint | not null
s_phone | character() |
s_acctbal | numeric |
s_comment | character varying() |
Checksum: t
Indexes:
"idx_supplier_nation_key" btree (s_nationkey)
Distributed by: (s_suppkey) testdb=# \d nation
Append-Only Columnar Table "public.nation"
Column | Type | Modifiers
-------------+------------------------+--------------------------------------------------------------
n_nationkey | bigint | not null default nextval('nation_n_nationkey_seq'::regclass)
n_name | character() |
n_regionkey | bigint | not null
n_comment | character varying() |
Checksum: t
Indexes:
"idx_nation_regionkey" btree (n_regionkey)
Distributed by: (n_nationkey) testdb=# \d region
Append-Only Columnar Table "public.region"
Column | Type | Modifiers
-------------+------------------------+--------------------------------------------------------------
r_regionkey | bigint | not null default nextval('region_r_regionkey_seq'::regclass)
r_name | character() |
r_comment | character varying() |
Checksum: t
Distributed by: (r_regionkey)

上面是查询中涉及到的5个表。

可以看到Greenplum使用的是列存储。

Append-Only意思是不断追加的表,不能进行更新和删除,压缩表必须是Append-Only表。我理解Greenplum主要是处理OLAP,为了能够有更大的吞吐量,使用列存储的表结构,而列存储就可以压缩,而压缩表又必须是Append-Only表,所以表的标题都使用Appen-Only Columnar Table XXX。

每个表最后都有Distributed by:(XXX),是表的分布键,即表是按照这个键值分布在不同的segment上的。

4.数据库连接图

5.查询分析结果

                                                                                  QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=46881.43..46881.53 rows= width=)
-> Gather Motion : (slice10; segments: ) (cost=46881.43..46881.53 rows= width=)
Merge Key: public.supplier.s_acctbal, public.nation.n_name, public.supplier.s_name, part.p_partkey
-> Limit (cost=46881.43..46881.44 rows= width=)
-> Sort (cost=46881.43..46881.44 rows= width=)
Sort Key (Limit): public.supplier.s_acctbal, public.nation.n_name, public.supplier.s_name, part.p_partkey
-> Hash Join (cost=42481.34..46881.39 rows= width=)
Hash Cond: "Expr_SUBQUERY".csq_c0 = part.p_partkey AND "Expr_SUBQUERY".csq_c1 = public.partsupp.ps_supplycost
-> HashAggregate (cost=23828.08..25828.08 rows= width=)
Group By: public.partsupp.ps_partkey
-> Redistribute Motion : (slice4; segments: ) (cost=18228.08..21428.08 rows= width=)
Hash Key: public.partsupp.ps_partkey
-> HashAggregate (cost=18228.08..18228.08 rows= width=)
Group By: public.partsupp.ps_partkey
-> Hash Join (cost=423.08..17428.08 rows= width=)
Hash Cond: public.partsupp.ps_suppkey = public.supplier.s_suppkey
-> Append-only Columnar Scan on partsupp (cost=0.00..11805.00 rows= width=)
-> Hash (cost=323.08..323.08 rows= width=)
-> Broadcast Motion : (slice3; segments: ) (cost=9.08..323.08 rows= width=)
-> Hash Join (cost=9.08..223.08 rows= width=)
Hash Cond: public.supplier.s_nationkey = public.nation.n_nationkey
-> Append-only Columnar Scan on supplier (cost=0.00..149.00 rows= width=)
-> Hash (cost=8.83..8.83 rows= width=)
-> Broadcast Motion : (slice2; segments: ) (cost=4.16..8.83 rows= width=)
-> Hash Join (cost=4.16..8.58 rows= width=)
Hash Cond: public.nation.n_regionkey = public.region.r_regionkey
-> Append-only Columnar Scan on nation (cost=0.00..4.25 rows= width=)
-> Hash (cost=4.11..4.11 rows= width=)
-> Broadcast Motion : (slice1; segments: ) (cost=0.00..4.11 rows= width=)
-> Append-only Columnar Scan on region (cost=0.00..4.06 rows= width=)
Filter: r_name = 'AFRICA'::bpchar
-> Hash (cost=18623.96..18623.96 rows= width=)
-> Redistribute Motion : (slice9; segments: ) (cost=4340.29..18623.96 rows= width=)
Hash Key: part.p_partkey
-> Hash Join (cost=4340.29..18584.88 rows= width=)
Hash Cond: public.partsupp.ps_suppkey = public.supplier.s_suppkey
-> Redistribute Motion : (slice6; segments: ) (cost=4092.22..18287.96 rows= width=)
Hash Key: public.partsupp.ps_suppkey
-> Hash Join (cost=4092.22..18092.59 rows= width=)
Hash Cond: public.partsupp.ps_partkey = part.p_partkey
-> Append-only Columnar Scan on partsupp (cost=0.00..11805.00 rows= width=)
-> Hash (cost=3970.11..3970.11 rows= width=)
-> Broadcast Motion : (slice5; segments: ) (cost=0.00..3970.11 rows= width=)
-> Append-only Columnar Scan on part (cost=0.00..3848.00 rows= width=)
Filter: p_size = AND p_type::text ~~ '%COPPER'::text
-> Hash (cost=223.08..223.08 rows= width=)
-> Hash Join (cost=9.08..223.08 rows= width=)
Hash Cond: public.supplier.s_nationkey = public.nation.n_nationkey
-> Append-only Columnar Scan on supplier (cost=0.00..149.00 rows= width=)
-> Hash (cost=8.83..8.83 rows= width=)
-> Broadcast Motion : (slice8; segments: ) (cost=4.16..8.83 rows= width=)
-> Hash Join (cost=4.16..8.58 rows= width=)
Hash Cond: public.nation.n_regionkey = public.region.r_regionkey
-> Append-only Columnar Scan on nation (cost=0.00..4.25 rows= width=)
-> Hash (cost=4.11..4.11 rows= width=)
-> Broadcast Motion : (slice7; segments: ) (cost=0.00..4.11 rows= width=)
-> Append-only Columnar Scan on region (cost=0.00..4.06 rows= width=)
Filter: r_name = 'AFRICA'::bpchar
Settings: enable_nestloop=off
Optimizer status: legacy query optimizer
( rows)

下面对于查询分析中的一些参数做一些说明:

slice:Greenplum在实现分布式执行计划的时候,需要将SQL拆分成多个切片,每个slice是单裤执行的一部分SQL,每一个广播或者重分布会产生一个切片,每一个切片在每一个数据结点上都会对应的发起一个进程来处理该slice负责的数据,上一层负责该slice的进程会读取下级slice广播或重分布的数据,之后进行相应的计算。

segment:这里使用的是1个mdw、2个sdw,每个sdw中设置两个primary(greenplum安装时gpinitsystem使用的文件中设置),所以看到的segment是4。

cost:数据库自定义的消耗单位,通过统计信息来估计SQL消耗。(查询分析是根据analyze的固执生成的,生成之后按照这个查询计划执行,执行过程中analyze是不会变的。所以如果估值和真是情况差别较大,就会影响查询计划的生成。)

rows:根据统计信息估计SQL返回结果集的行数。

width:返回结果集每一行的长度,这个长度值是根据pg_statistic表中的统计信息来计算的。

6.对查询计划的结果进行分析

1.逻辑架构图

根据这个查询分析,画出查询的逻辑架构图:

2.对上图做一些补充

上图中我只画出了一个节点的逻辑架构,并没有表现出广播和重分布,下面用例子说明广播和重分布

上面这个图是两个节点数据重分布的例子,这个图要完成的任务是两个表的Hash Join,由于某种原因(之后会讲述到)要将其中一个表的数据重分布到其他结点上去,以完成连接的任务。橘色部分是slice1,绿色部分是slice2,slice1中的表重分布之后到了slice2中,在slice2中做Hash Join,对连接之后的结果收集到master上。下图说明重分布时数据在slice之间传输的过程

所以这也是为什么在逻辑图中,redistribution和broadcast的椭圆处于两个slice的交界处,并且同时属于两个slice。

3.能够产生slice的操作是redistribution、broadcast和gather

  1.gather、broadcast和redistribution的介绍

gather:聚合,在master上讲子节点所有的数据聚合起来。一般的聚合规则是哪一个子节点的数据先返回到master上就将该节点的数据先放在master上。

broadcast:广播,发生在两表关联的时候。将每个节点上的某个表的数据全部发送到所有节点,这样每个节点都相当于有全量数据。一般,小表的时候采用广播的方法。(注:的是不论大表还是小表,最初都是分散在所有子节点上的)

redistribution:重分布,发生在两表关联的时候和group by的时候。当不满足广播的条件或者代价太大的时候,选择重分布,即按照新的分布键将各个节点上的数据重新打散到各个节点。

下面着重介绍broadcast和redistribution

  2. join的时候的广播和重分布

两表连接的时候可能广播可能重分布那么什么时候使用广播,什么时候使用重分布呢?在Query中使用到的都是Hash Join,所以我们暂时只讨论这种情况下的广播和重分布。

通俗的讲,两表连接,如果是其中一个是小表,则将其广播,因为小表广播代价不会很大;如果两个表都是大表可能要重分布。分三种情况讨论:

默认情况,我们认为主键id即为分布键

①select * from A,B where A.id = B.id     分布键就是关联键,两表可以在本结点直接连接

②select * from A,B where A.id = B.id2    A的分布键就是关联键,B的分布键不是关联键。所以不能将A重分布,有两种解决方案:

a.将A广播(如果A是小表)

b.将B重分布(如果A是大表)

最终权衡取代价最小的方案

③select * from A,B where A.id2 = B.id2     A和B的分布键都不是关联键。

a.将A或B都按照id2重分布

b.将min(A,B)广播(如果较小的表是小表)

最终权衡取代价最小的方案

这里只讲述了Hash Join的情况,除此之外还有left join和full outer join,需要了解在书中135页有详解。

  3.group by时候的重分布

在group by的时候也可能会产生重分布,下面介绍一下group by时重分布的原理:

group by时时先在本机上进行一个group by,然后重分布,用group by时使用的字段作为分布键重分布,重分布之后再做一次group by,所以group by操作在分布式的环境下其实是做两次group by的。书上119页图有例子说明group by的数据重分布情况。

4.分析Query2的查询计划

下面按照每个切片的方式分析Query2的查询计划。

slice1~4,包括slice10左分支的部分,是Query中的子查询部分;

slice5~9是父查询中where子句中除了ps_supplycost = (子查询)的部分;

slice10是ps_supplycost = (子查询)和父查询中的order by和limit。

①slice1和slice2:region表和nation表的hash join,数据库连接图可以看出应该属于上述第二种情况region.id=nation.id2,这里采用将region广播,说明相比重分布nation,广播region的代价更小;hash join的时候将region表hash,说明region表是个小表,这与广播region代价更小相吻合。

②slice3:hash join的原理同①

③slice4:hash join的原理同①,三次hash join实现了四个表的连接。之后做了聚合操作,对应子查询中min的操作,min的时候需要使用到group by,根据上面的介绍,知道group by在分布式环境下其实是做两次的,中间是一次重分布,这里可以在slice4和slice10的左子树看到有两次hashAggregate。

④slice5和slice6:hash join的原理同①

⑤slice7和slice8:hash join的同①

⑥slice9:左子树的hash join与slice3中的一样,不同的是连接之后,slice3进行了广播,slice9是进行了hash映射,造成这种差别的原因是这个连接表将要连接的表不同:

slice3中hash join的中间表与partsupp连接,是hash join中的第二种情况,中间表.id=partsupp.id2,由于中间表是小表,所以将中间表广播;

slice9中hash join的中间表1与slice6的中间表2连接,是hash join中的第二种情况,中间表1.id=中间表.id2,虽然中间表1是小表,但是没有广播中间表1,而是将中间表2重分布,因为中间表1广播到4个节点的代价总和大于将中间表2重分布。

⑦slice10,hash join之后,对每个子节点的数据按照四个键值排序,每个节点舍弃掉一部分数据(排序比较靠后,不可能包含在最后的limit结果中),将排序靠前的数据输出给master,这个过程叫做gather,使用排序时的四个键值作为merge key

取排序之后的一部分(这部分一定包含最终结果)输出给master,另一部分舍弃掉,不输出给master。master得到最终聚合的数据,再进行一次limit操作,这次limit得到的是query需要的100条数据。

以上就是对Query2的查询计划结果的分析,还有一些问题尚未弄清楚:

1.关联键是否默认是主键?是(书上201页)

2.列存储是否有主键,是否有完整性约束?应该是有的,因为列存储取到相应属性之后还要将他们组合成行表。

3.在查询计划的结果中,为什么query中的min(ps_supplycost)体现在查询计划中是group by(ps_partkey)而非group by(ps_supplycost)

4.书上讲的hashAggregate和groupAggregate的原理不是很清楚

5.slice10中的hash join的hash key不明白

6.order by多个键值是怎么排序的

心得:

1.觉得画图写东西有点浪费时间,但是发现不画出来写出来,其实有些东西理解的不够,或者有偏差。记录下来有助于深入理解知识

2.写东西相当于对知识体系的一次整合,没整理的时候,所有东西在大脑不是混沌的,真理之后知识变得逻辑、有序。

Greenplum查询计划分析的更多相关文章

  1. SQLServer查询执行计划分析 - 案例

    SQLServer查询执行计划分析 - 案例 http://pan.baidu.com/s/1pJ0gLjP 包括学习笔记.书.样例库

  2. 看懂SqlServer查询计划 SQL语句优化分析

    转自 http://www.cnblogs.com/fish-li/archive/2011/06/06/2073626.html 阅读目录 开始 SQL Server 查找记录的方法 SQL Ser ...

  3. 记一次SqlServer大表查询语句优化和执行计划分析

    数据库: sqlserver2008r2 表: device_data 数据量:2000w行左右 表结构 CREATE TABLE [dbo].[device_data]( [Id] [int] ID ...

  4. Greenplum 数据库架构分析

    Greenplum 数据库是最先进的分布式开源数据库技术,主要用来处理大规模的数据分析任务,包括数据仓库.商务智能(OLAP)和数据挖掘等.自2015年10月正式开源以来,受到国内外业内人士的广泛关注 ...

  5. 开源大数据引擎:Greenplum 数据库架构分析

    Greenplum 数据库是最先进的分布式开源数据库技术,主要用来处理大规模的数据分析任务,包括数据仓库.商务智能(OLAP)和数据挖掘等.自2015年10月正式开源以来,受到国内外业内人士的广泛关注 ...

  6. SQL Server-聚焦查询计划Stream Aggregate VS Hash Match Aggregate(二十)

    前言 之前系列中在查询计划中一直出现Stream Aggregate,当时也只是做了基本了解,对于查询计划中出现的操作,我们都需要去详细研究下,只有这样才能对查询计划执行的每一步操作都了如指掌,所以才 ...

  7. MySQL的查询计划中ken_len的值计算

    本文首先介绍了MySQL的查询计划中ken_len的含义:然后介绍了key_len的计算方法:最后通过一个伪造的例子,来说明如何通过key_len来查看联合索引有多少列被使用. key_len的含义 ...

  8. 看懂SqlServer查询计划【转】

    原文链接:http://www.cnblogs.com/fish-li/archive/2011/06/06/2073626.html 开始 SQL Server 查找记录的方法 SQL Server ...

  9. SQL查询性能分析

    http://blog.csdn.net/dba_huangzj/article/details/8300784 SQL查询性能的好坏直接影响到整个数据库的价值,对此,必须郑重对待. SQL Serv ...

随机推荐

  1. kali安装vmtools问题

    切记使用此法,一定要确保kali没有装过vmware workstation自带的vmware_tools,不然要卸载之后才能使用.我当初就是安装了后使用此法,不能成功,卸载也不行,导致完全重装 安装 ...

  2. centos7 开放端口

    开启端口 firewall-cmd --zone=public --add-port=80/tcp --permanent 命令含义:   --zone #作用域   --add-port=80/tc ...

  3. Android消息处理

    基本概念: Message:消息,其中包含了消息ID.what,消息处理对象.obj以及处理的数据.arg1.arg2等,由MessageQueue统一列队,终由Handler处理. Handler: ...

  4. Linux学习之四--Nginx

    关于Nginx的 nginx的是一个高性能的Web服务器的软件.它比Apache HTTP服务器更加灵活,重量轻的程序. 本教程将教你如何安装和你的CentOS 7服务器上启动Nginx的.   先决 ...

  5. 移动端浏览器body的overflow:hidden并没有什么作用

    今天突然遇到一个问题,使用li模拟select,但是碰到一个很尴尬的问题,给body加了overflow:hidden,但是body并没有禁止滚动条,滚动条依旧顺滑. <!DOCTYPE htm ...

  6. 3篇NeuroImage文献分析

    鉴于之前读的一些文章很容易就忘掉了,故打算花点时间记录下所读的文献. 这几天花了一些时间读了3篇文献: Intersubject consistency of cortical MEG signals ...

  7. BZOJ 4668: 冷战

    Description 在一个图上,在两个点间连一条边,问这两个点最早在什么时候联通. Sol 并查集+启发式合并. 按秩合并的并查集...我也不知道什么是按秩合并,反正就跟启发式合并差不多,合并的时 ...

  8. spring mvc 中文参数乱码

    最近做项目,springmvc的url中文参数乱码: 请求url: http://localhost:8080/supply/supply_list.htm?productName=测试&is ...

  9. 第3章 拍摄UFO——单一职责原则

    就一个类而言,应该仅有一个引起它变化的原因

  10. go:channel(未完)

    注:1)以下的所有讨论建立在包含整形元素的通道类型之上,即 chan int 2)对于“<-”我的理解是,它可能是一个操作符(接收操作符),也  可能是类型的一部分(如“chan<- in ...