Hive SQL优化之 Count Distinct
說實話,也是從今年4月份開始,筆者接觸了大量寫SQL的工作,才開始慢慢理解SQL的優化。在之前,公司的大數據平臺只有十幾個節點,隨著業務快速發展,每天都會產生上百萬條的數據,所以每天使用Hive寫SQL發現執行時間都在變慢,但是以結果為目的的工作,在不追求高效的情況下,沒人有去深入思考如何改變這種狀態,每次感覺慢的實在受不了才去申請加節點,因為Hive是構建在Hadoop分布式集群上的,是一種分布式環境,加節點的目的就在橫向擴展,久而久之,習慣了這種方式導致的結果就是寫的SQL執行效率越來越低。
如何寫出高效的SQL,不僅需要我們去思考,也需要我們思考如何高效的工作,下面重點來說一下Hive SQL中的count(distinct id)的優化。
日常統計場景中,我們經常會對一段時期內的字段進行消重并統計數量,SQL語句類似于:
SELECT COUNT( DISTINCT id ) FROM TABLE_NAME WHERE ...;?這條語句是從一個表的符合WHERE條件的記錄中統計不重復的id的總數。
該語句轉化為MapReduce作業后執行示意圖如下,圖中還列出了我們實驗作業中Reduce階段的數據規模:
由于引入了DISTINCT,因此在Map階段無法利用combine對輸出結果消重,必須將id作為Key輸出,在Reduce階段再對來自于不同Map Task、相同Key的結果進行消重,計入最終統計值。
我們看到作業運行時的Reduce Task個數為1,對于統計大數據量時,這會導致最終Map的全部輸出由單個的ReduceTask處理。這唯一的Reduce Task需要Shuffle大量的數據,并且進行排序聚合等處理,這使得它成為整個作業的IO和運算瓶頸。
經過上述分析后,我們嘗試顯式地增大Reduce Task個數來提高Reduce階段的并發,使每一個Reduce Task的數據處理量控制在2G左右。具體設置如下:
?調整后我們發現這一參數并沒有影響實際Reduce Task個數,Hive運行時輸出“Number of reduce tasks determined at compile time: 1”。原來Hive在處理COUNT這種“全聚合(full aggregates)”計算時,它會忽略用戶指定的Reduce Task數,而強制使用1。我們只能采用變通的方法來繞過這一限制。我們利用Hive對嵌套語句的支持,將原來一個MapReduce作業轉換為兩個作業,在第一階段選出全部的非重復id,在第二階段再對這些已消重的id進行計數。這樣在第一階段我們可以通過增大Reduce的并發數,并發處理Map輸出。在第二階段,由于id已經消重,因此COUNT(*)操作在Map階段不需要輸出原id數據,只輸出一個合并后的計數即可。這樣即使第二階段Hive強制指定一個Reduce Task,極少量的Map輸出數據也不會使單一的Reduce Task成為瓶頸。改進后的SQL語句如下:
在實際運行時,我們發現Hive還對這兩階段的作業做了額外的優化。它將第二個MapReduce作業Map中的Count過程移到了第一個作業的Reduce階段。這樣在第一階Reduce就可以輸出計數值,而不是消重的全部id。這一優化大幅地減少了第一個作業的Reduce輸出IO以及第二個作業Map的輸入數據量。最終在同樣的運行環境下優化后的語句執行只需要原語句20%左右的時間。優化后的MapReduce作業流如下:
?從上述優化過程我們可以看出,一個簡單的統計需求,如果不理解Hive和MapReduce的工作原理,它可能會比優化后的執行過程多四、五倍的時間。我們在利用Hive簡化開發的同時,也要盡可能優化SQL語句,提升計算作業的執行效率。
總結
以上是生活随笔為你收集整理的Hive SQL优化之 Count Distinct的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql中ifnull和hive中if
- 下一篇: SQL实战篇:SQL基础及执行顺序