最近在作大數據處理時,遇到兩個大表 join 致使數據處理太慢(甚至算不出來)的問題。咱們的數倉基於阿里的 ODPS,它與 Hive 相似,因此這篇文章也適用於使用 Hive 優化。處理優化問題,通常是先指定一些經常使用的優化參數,可是當設置參數仍然不奏效的時候,咱們就要結合具體的業務,在 SQL 上作優化了。爲了避免增長你們的閱讀負擔,我會簡化這篇文章的業務描述。sql
這是一個離線數據處理的問題。在這個業務中有兩張表,表結構及說明以下:bash
user_article_tb 表:複製代碼
字段解釋:
uid: 用戶標識,itemid:文章id,dur: 閱讀文章時長,若是大於 0 表明閱讀了文章,等於 0 表明沒有點擊文章
dt:天分區,天天 55 億條記錄複製代碼
user_profile_tb 表:複製代碼
字段解釋:
uid:用戶標識,gender:性別,F 表明女,M 表明男,age:年齡,city:城市
dt:天分區字段,這是一張總表,天天存儲全量用戶畫像屬性,最新數據十億級別複製代碼
需求是這樣的:計算 7 天中,女性用戶在每篇文章上的 ctr (最終會按照降序進行截斷)。直接寫 SQL 很容易,以下:markdown
select itemid , count(if(dur > 0, 1, null)) / count(1) ctr from ( select uid, itemid, dur from user_article_tb where dt>='20190701' and dt<='20190707' ) data_tb join ( select * from user_profile_tb where dt='20190707' --最新的日期 and gender='F' ) profile_tb on data_tb.uid = profile_tb.uid group by itemid order by ctr desc limit 50000 ;複製代碼
那麼問題來了:大數據
咱們一一解決上面提到的兩個問題。先考慮第一個,既然 join 的兩張表太大了,咱們能不能嘗試把表變小呢。答案是確定的,對於畫像表來講顯然是沒辦法縮小了,可是對於 user_artitle_tb 是能夠的。咱們能夠按照表的分區字段 dt 用天天的數據分別 join 畫像表,將結果再按天存儲在一張臨時表裏面。這樣天天就是十億級別的數據 join,基本能夠解決問題。可是天天的數據仍有多餘的 join,好比:某天的數據中 uid = 00001 的用戶,一天看了 1000 篇文章,那這個用戶就須要多 join 999 次。在咱們的業務中一個用戶一天看文章的數量 > 10 是很廣泛的,所以多餘 join 的狀況仍是比較嚴重的。優化
針對上面提到的多餘 join 的狀況,最完全的解決方法就是把 user_article_tb 表變成 uid 粒度的,跟畫像表同樣。咱們將 7 天的數據轉換成 uid 粒度的 SQL 以下:ui
insert overwrite table user_article_uid_tb as select uid, wm_concat(':', concat_ws(',', itemid, dur)) item_infos from ( select * from user_article_tb where dt >= '20190701' and dt <= '20190707' ) tmp group by uid複製代碼
從上面 SQL 能夠看到,咱們首先將 7 天的數據按照 uid 作 group by 操做,構造 item_infos。由於咱們的是計算 ctr,因此咱們能夠按照 uid 粒度對錶作轉換,而且 item_infos 字段包含什麼是要根據業務需求作選擇。天天不到 1 億 uid,7天彙總的 uid 不到 10 億,兩張 uid 粒度的表進行 join 就會快不少。spa
至此,多餘 join 的問題獲得瞭解決, 再來看看第二個問題。這個問題其實就是咱們維度建模理論中所說的寬表,爲了不統計不一樣維度時頻繁 join 維表,咱們能夠在上游數據將經常使用的維度提早關聯起來,造成一張大寬表。下游數據能夠直接用從而減小 join。以咱們的問題爲例,SQL 以下:code
create table user_profile_article_uid_tb as select data_tb.uid , item_infos , gender , age , city -- 其餘維度字段 from ( select uid, item_infos from user_article_uid_tb ) data_tb join ( select uid, gender, age, city from user_profile_tb where dt='20190707' --最新的日期 ) profile_tb on data_tb.uid = profile_tb.uid;複製代碼
這樣,上面提到的兩個問題就都解決了。最終咱們的需求:女性用戶每篇文章的 ctr 計算以下:orm
select itemid , count(if(dur > 0, 1, null)) / count(1) ctr from ( select split(item_info, ',')[0] itemid , split(item_info, ',')[1] dur from user_profile_article_uid_tb lateral view explode(split(item_infos, ':')) item_tb as item_info ) tmp group itemid order by ctr desc limit 50000複製代碼
mapreduce.map.memory.mb
mapreduce.reduce.memory.mb
mapred.reduce.tasks複製代碼
這些參數設置是比較通用的選項, 當這些選項不可以達到最優的效果時,須要從業務上進行優化。ci
這篇文章主要介紹了在 ODPS 或 Hive 上,百億級數據規模的 join 優化。核心思想就是減小 join 的數據量,同時優化沒有放之四海而皆準的方法,必定是結合業務進行的。
歡迎關注公衆號「渡碼」,一塊兒見證成長