最近在作大数据处理时,遇到两个大表 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 的数据量,同时优化没有放之四海而皆准的方法,必定是结合业务进行的。
欢迎关注公众号「渡码」,一块儿见证成长