當兩張表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是否生效,樣本如下: