摘要: Join是MaxCompute中最基本的語法,但因爲數據量和傾斜問題,很是容易出現性能問題。通常狀況下,join產生的問題有兩大類: 數據傾斜問題:join會將key相同的數據分發到同一個instance上處理,若是某個key上的數據量特別多則會致使該instance處理時間比其餘instance...算法
原文地址:http://click.aliyun.com/m/43804/ sql
Join是MaxCompute中最基本的語法,但因爲數據量和傾斜問題,很是容易出現性能問題。通常狀況下,join產生的問題有兩大類:性能
雖然MaxCompute中提供了一些通用的優化算法,但從業務角度解決性能問題每每更精確,更有效。對於MaxCompute sql優化,在雲棲社區上已經有比較多的經驗積累,本文主要對join產生的性能問題以及解法作些總結。優化
瀏覽IPV日誌以商品id關聯商品表,假設日誌表的商品id字段是string類型,商品表中的商品id是bigint類型,那麼在關聯中,關聯key會所有轉換成double類型進行比較,設想因爲埋點問題日誌表中的商品id存在不少非數值的髒數據,那麼轉換成double後值都變爲NULL或者截取前面的數值,關聯時就會產生數據傾斜問題,更嚴重的會形成數據錯誤。spa
關聯時手工進行數據格式轉換,在這種狀況下通常將bigint類型key轉換成string類型。3d
select a.* from ipv_log_table a left outer join item_table b on a.item_id = cast(b.item_id as string)
思考下,假如反過來將string類型轉換成bigint、假如IPV日誌表中的商品id大部分爲無效值(好比0)、又假如IPV日誌表中沒有無效值可是有熱點key會有什麼問題呢?下面的例子會解答這些問題。日誌
小表join大表code
Join中存在小表,通常這個小表在100M之內,能夠用mapjoin,避免分發引發的長尾。拿上面的例子來講,假如商品表數據量只有幾萬條記錄(這裏只是打個比方,現實業務中商品表通常都是很是龐大的),可是IPV日誌表中的商品id 80%值爲0的無效值,且記錄數有幾十億,若是採用上述SQL寫法,數據傾斜是顯而易見的,但利用mapjoin能夠有效解決這個問題:blog
select /*+ MAPJOIN(b) */a.* from ipv_log_table a left outer join item_table b on a.item_id = cast(b.item_id as string)
把小表廣播傳遞到全部的Join Task Instance上面,而後直接和大表作Hash Join,簡單的說就是將join操做提早到map端,而非reduce端。ip
left outer join
時只能是右表, right outer join
時只能是左表, inner join
時無限制,full outer join
不支持mapjoin;在小表join大表時咱們已經瞭解到經過mapjoin將小表所有加載到map端能夠解決傾斜問題,但假如‘小表‘不夠小,mapjoin失效的時候該怎麼辦呢?一樣以本文第一個場景爲例,IPV日誌表中80%商品id都爲無效值0(目前MaxCompute底層已經針對NULL值進行優化,已經不存在傾斜問題了),這時關聯十幾億量級商品表那就是個災難。
咱們能夠事先知道無效值是不可能關聯出結果的,並且徹底不須要參與關聯,因此能夠將無效值與有效值數據分開處理:
select a.visitor_id ,b.seller_id from ( select from ipv_log_table where item_id > 0 ) a left outer join item_table b on a.item_id = b.item_id union all select a.visitor_id ,cast(null as bigint) seller_id from ipv_log_table where item_id = 0
咱們也能夠以隨機值代替NULL值做爲join的key,這樣就從原來一個reduce來處理傾斜數據變成多reduce並行處理,由於無效值不參與關聯,即便分發到不一樣reduce,也不會影響最終計算結果:
select a.visitor_id ,b.seller_id from ipv_log_table a left outer join item_table b on if(a.item_id > 0, cast(a.item_id as string), concat('rand',cast(rand() as string))) = cast(b.item_id as string)
雖然商品表有十幾億條記錄,不能直接經過mapjoin來處理,但在實際業務中,咱們知道一天內用戶訪問的商品數是有限的,在業務中尤其明顯,基於此咱們能夠經過一些處理轉換成mapjoin:
select /*+ MAPJOIN(b) */ a.visitor_id ,b.seller_id from ipv_log_table a left outer join ( select /*+ MAPJOIN(log) */ itm.seller_id ,itm.item_id from ( select item_id from ipv_log_table where item_id > 0 group by item_id ) log join item_table itm on log.item_id = itm.item_id ) b on a.item_id = b.item_id
解法1和解法2是通用解決方案,對於解法1,日誌表被讀取兩次,而解法2中只需讀取一次,另外任務數解法2也是少於解法1的,因此總的來看解法2是優於解法1的。解法3是基於必定的假設,隨着業務發展或者某些特殊狀況下假設可能失效(好比一些爬蟲日誌,可形成訪問商品數接近全量),這會致使mapjoin失效,因此在使用過程當中要根據具體狀況來評估。
最後要講一個古老的優化case,雖然歷史比較久遠,目前已沒有相關問題,但優化思路值得借鑑。狀況是這樣的,歷史上並存過兩套商品維表,一份主鍵是字符串id,新的商品表也就是目前在使用的主鍵是數字id,字符串id和數字id作了映射,存在商品表中的兩個字段中,因此在使用中須要分別過濾數字id、字符串id而後分別和商品表關聯,最後union起來獲得最終結果。
思考下若是換成下面的優化思路是否是更優呢?
select ... from ipv_log_table a join ( select auction_id as auction_id from auctions union all select auction_string_id as auction_id from auctions where auction_string_id is not null ) b on a.auction_id = b.auction_id
答案是確定的,能夠看到優化後商品表讀取從2次降爲1次,IPV日誌表一樣,另外MR做業數也從2個變爲1個。
對於MaxCompute sql優化最有效的方式是從業務的角度切入,並可以將MaxCompute sql轉化爲mapreduce程序來解讀。本文針對join中各類場景優化都作了一些梳理,現實狀況極可能是上述多場景的組合,這時候就須要靈活運用相應的優化方法,觸類旁通。
識別如下二維碼,閱讀更多幹貨