最近在作大數據處理時,遇到兩個大表 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 很容易,以下:大數據
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 的狀況仍是比較嚴重的。ui
針對上面提到的多餘 join 的狀況,最完全的解決方法就是把 user_article_tb 表變成 uid 粒度的,跟畫像表同樣。咱們將 7 天的數據轉換成 uid 粒度的 SQL 以下:spa
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 就會快不少。code
至此,多餘 join 的問題獲得瞭解決, 再來看看第二個問題。這個問題其實就是咱們維度建模理論中所說的寬表,爲了不統計不一樣維度時頻繁 join 維表,咱們能夠在上游數據將經常使用的維度提早關聯起來,造成一張大寬表。下游數據能夠直接用從而減小 join。以咱們的問題爲例,SQL 以下:cdn
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 計算以下:blog
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 的數據量,同時優化沒有放之四海而皆準的方法,必定是結合業務進行的。
歡迎關注公衆號「渡碼」,一塊兒見證成長