两种分布方式

在Greenplum数据库中所有表都是分布式的,所以每一张表都会被切片,每个segment实例数据库会存放相应的数据片段。切片(分布)规则可以由用户定义,可选的方案有根据用户对每一张表指定的hash key进行的Hash分布或者选择随机分布。

Hash分布

当选择Hash分布策略时,可以指定表的一列或者多列组合。greenplum会根据指定的Hash key列计算每一行数据对应的Hash值,并映射到相应的segment实例。当选择的Hash key列的值唯一时,数据会均匀的分散至所有segment实例。
对于分布键的选择,有以下方式及行为:
1.指定分布键,分布键可以是表的一列或者多列组合,但不建议组合分布键的列数超过两列。
2.若表中存在主键,不能指定其他单列作为唯一主键,且对于组合分布键,其中必须要包含主键,且主键必须要位于组合分布键的第一列,否则会报错。
3.若没有指定分布键,且表中没有主键及唯一键,则默认使用第一列作为分布键。
4.若没有指定分布键,且表中存在主键或唯一键(二者不能同时存在),则选择主键或唯一键作为分布键。
总结来说,对于分布键的优先级如下:
主键 > 指定分布键 > 唯一键 > 未指定分布键

随机分布

当选择随机分布时,数据将会随机分布至segment,相同值的数据行不一定会分发至同一个segment。虽然随机分布可以确保数据平均分散至所有segment,但是在进行表关联分析时,仍然会按照关联键重分布数据,所以随机分布策略通常不是一个明智的选择(除非你的SQL只有对单表进行全局的聚合操作,即没有group by或者join等需要数据重分部的操作)。

数据分布的最佳实践

能致使数据被均匀分布的最优分布是Greenplum数据库中最重要的因素。在一个MPP无共享环境中,一个查询的总体响应时间由所有Segment的完成时间度量。整个系统和最慢的一个Segment一样快。如果数据发生倾斜,拥有更多数据的Segment将需要更多时间完成,因此每一个Segment必须有大约相同数据量的行并且执行大概相同量级的处理。如果一个Segment比其他Segment有明显更多的数据要处理,将会导致糟糕的性能和内存不足的情况。

在决定分布策略时,考虑下列最佳实践:
1.为所有的表明确定义一个分布列或者随机分布。不要使用默认分布。
2.理想情况下,使用单个将数据在所有Segment之间均匀分布的列。
3.不要在查询的WHERE子句中将要使用的列上进行分布。
4.不要在日期或者时间戳上分布。
5.分布键列数据应该含有唯一值或者非常高的势。
6.如果单个列无法实现均匀分布,则使用多列分布键,但不要超过两列。额外的列值通常不会得到更均匀的分布,而且它们要求额外的哈希处理时间。
7.如果两个列的分布键无法实现数据的均匀分布,则使用随机分布。大部分情况中的多列分布键都要求移动操作来连接表,因此它们对于随机分布来说没有优势。

Greenplum数据库的随机分布不是循环的,因此无法保证每个Segment上的记录数相等。随机分布通常会落在变化低于10个百分点的目标范围中。

在连接大型表时,最优分布非常关键。为了执行连接,匹配的行必须位于同一个Segment上。如果数据没有按照同一个连接列分布,其中一个表中需要的行会被动态重新分布到其他Segment上。在一些情况下会执行一次广播移动而不是执行重新分布移动。

关联的广播与重分布

当两张表关联时,如果有一张表关联键不是分布键,那么就会发生表的广播或者重分布,将数据移动到一个节点上进行关联,从而获得数据。
分布式的关联有两种:
单库关联:关联键与分布键一致,只需要在单个库的关联后得到结果即可。数据无需在segment间移动,只在各个segment上执行即可。
跨库关联:关联键与分布键不一致,数据需要重新分布,转换成单库关联,从而实现表的关联。
通过下表中的两个表来进行SQL分析

表名

字段

分布键

数据量

A

id,id2

id

M

B

id,id2

id

N

内连接

情况1:

select * from A,B where A.id=B.id;

分布键与关联键相同,属于单库关联,不会造成广播或者重分布。

情况2:

select * from  A,B where A.id=B.id2;

表A的关联键是分布键,表B的关联键不是分布键,那么可以通过两种方法来实现表的关联:
1.将表B按照id2字段将数据重分布到每一个节点上,然后再与表A进行关联。重分布的数据量是N
2.将表A广播,每一个节点都放一份全量数据,然后再与表B关联得到结果。广播的数据量是 M * 节点数
所以当N>M * 节点数 的时候,选择表A广播,否则选择表B重分布。

情况3:

select * from A,B where A.id2=B.id2;

对于这种情况,两个表的关联键及分布键都不一样,那么还有两种做法:
1.将表A与表B都按照id2字段,将数据重分布到每个节点,重分布的代价是M+N
2.将其中一个表广播后再关联,当然选取小表广播,代价小,广播的代价是 min(M,N) * 节点数
所以,当 N + M > min(M,N) * 节点数 的时候,选择小表广播,否则选择两个表都重分布。

左连接:

情况1:

select * from A left join B on A.id=B.id;

单库关联,不涉及数据跨库关联。
情况2:

select * from A left join B on A.id=B.id2;

由于左表的分布键是关联键,鉴于左连接的性质,无论表B数据量多大,都必须将表B按照字段id2重分布数据。

情况3:

select * from A left join B on A.id2=B.id;

左表的关联键不是分布键,由于左连接A表肯定是不能被广播的,所以有两种方式:

1.将表A按照id2重分布数据,转换成情况A,代价为M
2.将表B广播,代价为 N * 节点数
情况4:

select * from A left join B on A.id2=B.id2;

还是有两种方式:
1.将表A与表B都按照id2字段将数据重分布一遍,转换成情况1,代价是M+N
2.表A不能被广播,只能将表B广播,代价是 N * 节点数
对于有多种情况,greenplum总是选择代价小的方式来执行sql。

全连接:

情况1:

select * from A full outer join B on A.id=B.id;

对于关联键都是分布键的情况,在gp中全连接只能采用Merger Join来实现

情况2:

select * from A full outer join B on A.id = B.id2;

将关联键不是分布键的表重分布数据,转换成情况1来解决。无论A、B大小分别为多少,为了实现全连接,不能将表广播,只能是重分布。

情况3:

select * from A full outer join B on A.id2=B.id2;

将两张表都重分布,转换成情况1进行处理。

参考:
1.http://www.dbdream.com.cn/2016/01/20/greenplum数据库创建表及表的数据分布/

2.《greenplum企业应用实战》
3.《greenplum数据库最佳实践》