当两张表Join存在热点,导致出现长尾问题时,您可以通过取出热点key,将数据分为热点数据和非热点数据两部分处理,最后合并的方式,提高Join效率。SkewJoin Hint可以通过自动或手动方式获取两张表的热点key,分别计算热点数据和非热点数据的Join结果并合并,加快Join的执行速度。
使用方法
您需要在select
语句中使用Hint提示/*+ skewJoin(<table_name>[(<column1_name>[,<column2_name>,...])][((<value11>,<value12>)[,(<value21>,<value22>)...])]*/
才会执行SkewJoin。table_name为倾斜表名,column_name为倾斜列名,value为倾斜key值。
--方法1:Hint表名(注意Hint的是表的alias)。
select /*+ skewjoin(a) */ * from T0 a join T1 b on a.c0 = b.c0 and a.c1 = b.c1;
--方法2:Hint表名和认为可能产生倾斜的列,例如表a的c0和c1列存在数据倾斜。
select /*+ skewjoin(a(c0, c1)) */ * from T0 a join T1 b on a.c0 = b.c0 and a.c1 = b.c1 and a.c2 = b.c2;
--方法3:Hint表名和列,并提供发生倾斜的key值。如果是STRING类型,需要加上引号。例如(a.c0=1 and a.c1="2")和(a.c0=3 and a.c1="4")的值都存在数据倾斜。
select /*+ skewjoin(a(c0, c1)((1, "2"), (3, "4"))) */ * from T0 a join T1 b on a.c0 = b.c0 and a.c1 = b.c1 and a.c2 = b.c2;
方法3直接指定值的处理效率比方法1和方法2(不指定值)高。
实现原理
热值Key指出现次数很多的key值。例如下图中红色部分,a.c0=1 and a.c1=2
有10000行,a.c0=3 and a.c1 = 4
有9000行。
在不加SkewJoin Hint的情况下,将表T0和表T1进行Join,由于T0和T1的数量都很大,只能进行MergeJoin,因此相同的热值都会Shuffle到一个节点,导致数据倾斜。加SkewJoin Hint后,优化器会运行一个Aggregate动态获取重复行数前20的热值,并将表T0中属于热值的值(数据A)、T0中不属于热值的值(数据B)拆分;将表T1中能与T0中热值Join的值(数据C)、表T1中不能与T0中热值Join的值(数据D)进行拆分。然后将数据A与数据C进行MapJoin(由于数据C量很少,可以进行MapJoin),将数据B和数据D进行MergeJoin。最后将MapJoin和MergeJoin的结果Union,生成最后的结果,如下图所示。
注意事项
SkewJoin Hint支持的Join类型:
Inner Join可以对Join两侧表中的任意一侧进行Hint。
Left Join、Semi Join和Anti Join只可以Hint左侧表。
Right Join只可以Hint右侧表。
Full Join不支持Skew Join Hint。
建议只对一定会出现数据倾斜的Join添加Hint,因为Hint会运行一个Aggregate,存在一定代价。同时,对于A Join B的Query,如果为A加了SkewJoin Hint,会强制产生一个类似于
MapJoin union all MergeJoin
的物理执行计划。此处的MapJoin子计划展开之后类似于:Top 20(A) MapJoin (B Semi Join Top20(A))
。如果B表本身数据量很大,经过Top20(A)
过滤之后的数据量也很大,则有可能导致MapJoin在构建HashJoin和HashTable时出现OOM的问题。被Hint的Join的Left Side Join Key的类型需要与Right Side Join Key的类型一致,否则SkewJoin Hint不生效。例如上例中的a.c0与b.c0的类型需要一致,a.c1与b.c1的类型需要一致。您可以通过在子查询中将Join key进行Cast从而保持一致。示例如下:
create table T0(c0 int, c1 int, c2 int, c3 int); create table T1(c0 string, c1 int, c2 int); --方法1: select /*+ skewjoin(a) */ * from T0 a join T1 b on cast(a.c0 as string) = cast(b.c0 as string) and a.c1 = b.c1; --方法2: select /*+ skewjoin(b) */ * from (select cast(a.c0 as string) as c00 from T0 a) b join T1 c on b.c00 = c.c0;
加SkewJoin Hint后,优化器会运行一个Aggregate获取前20的热值。20是默认值,您可以通过
set odps.optimizer.skew.join.topk.num = xx;
进行设置。SkewJoin Hint只支持对Join其中一侧进行Hint。
被Hint的Join一定要有
left key = right key
,不支持笛卡尔积Join。与其它Hint一起使用的方法如下,但需要注意,被MapJoin Hint的Join不能再添加SkewJoin Hint。
select /*+ mapjoin(c), skewjoin(a) */ * from T0 a join T1 b on a.c0 = b.c3 join T2 c on a.c0 = c.c7;
您可以在Logview的Json Summary中搜索是否出现topk_agg字段判断SkewJoin Hint是否生效,示例如下: