Hive中小表与大表关联(join)的性能分析zz

其实hive优化的建议是多表关联的时候将大表放在后面,因为前面的关联结果要放入内存。不过博主文章本身写的也不错。

经常看到一些Hive优化的建议中说当小表与大表做关联时,把小表写在前面,这样可以使Hive的关联速度更快,提到的原因都是说因为小表可以先放到内存中,然后大表的每条记录再去内存中检测,最终完成关联查询。这样的原因看似合理,但是仔细推敲,又站不住脚跟。

多小的表算小表?如果所谓的小表在内存中放不下怎么办?我用2个只有几条记录的表做关联查询,这应该算是小表了,在查看reduce的执行日志时依然是有写磁盘的操作的。实际上reduce在接收全部map的输出后一定会有一个排序所有键值对并合并写入磁盘文件的操作。写入磁盘(spill)有可能是多次的,因此有可能会生成多个临时文件,但是最终都要合并成一个文件,即最终每一个reduce都只处理一个文件。

我做了一个实验,用1条记录的表和3亿多条记录的表做join,无论小表是放在join的前面还是join的后面,执行的时间几乎都是相同的。再去看reduce的执行日志,1条记录的表在join前或者join后两次查询的reduce日志几乎也是一摸一样的。如果按照上面的说法把join左侧的表放内存等待join右侧的表到内存中去检测,那么当3亿多条记录的表放在join左侧时,内存肯定是无法容下这么多记录的,势必要进行写磁盘的操作,那它的执行时间应该会比小表在join前时长很多才对,但事实并不是这样,也就说明了上面说到的原因并不合理。

事实上“把小表放在前面做关联可以提高效率”这种说法是错误的。正确的说法应该是“把重复关联键少的表放在join前面做关联可以提高join的效率。”

分析一下Hive对于两表关联在底层是如何实现的。因为不论多复杂的Hive查询,最终都要转化成mapreduce的JOB去执行,因此Hive对于关联的实现应该和mapreduce对于关联的实现类似。而mapreduce对于关联的实现,简单来说,是把关联键和标记是在join左边还是右边的标识位作为组合键(key),把一条记录以及标记是在join左边还是右边的标识位组合起来作为值(value)。在reduce的shuffle阶段,按照组合键的关联键进行主排序,当关联键相同时,再按照标识位进行辅助排序。而在分区段时,只用关联键中的关联键进行分区段,这样关联键相同的记录就会放在同一个value list中,同时保证了join左边的表的记录在value list的前面,而join右边的表的记录在value list的后面。

例如A join B ON (A.id = b.id) ,假设A表和B表都有1条id = 3的记录,那么A表这条记录的组合键是(3,0),B表这条记录的组合键是(3,1)。排序时可以保证A表的记录在B表的记录的前面。而在reduce做处理时,把id=3的放在同一个value list中,形成 key = 3,value list = [A表id=3的记录,B表id=3的记录]

接下来我们再来看当两个表做关联时reduce做了什么。Reduce会一起处理id相同的所有记录。我们把value list用数组来表示。

1)   Reduce先读取第一条记录v[0],如果发现v[0]是B表的记录,那说明没有A表的记录,最终不会关联输出,因此不用再继续处理这个id了,读取v[0]用了1次读取操作。

2)   如果发现v[0]到v[length-1]全部是A表的记录,那说明没有B表的记录,同样最终不会关联输出,但是这里注意,已经对value做了length次的读取操作。

3)   例如A表id=3有1条记录,B表id=3有10条记录。首先读取v[0]发现是A表的记录,用了1次读取操作。然后再读取v[1]发现是B表的操作,这时v[0]和v[1]可以直接关联输出了,累计用了2次操作。这时候reduce已经知道从v[1]开始后面都是B 表的记录了,因此可以直接用v[0]依次和v[2],v[3]……v[10]做关联操作并输出,累计用了11次操作。

4)   换过来,假设A表id=3有10条记录,B表id=3有1条记录。首先读取v[0]发现是A表的记录,用了1次读取操作。然后再读取v[1]发现依然是A表的记录,累计用了2次读取操作。以此类推,读取v[9]时发现还是A表的记录,累计用了10次读取操作。然后读取最后1条记录v[10]发现是B表的记录,可以将v[0]和v[10]进行关联输出,累计用了11次操作。接下来可以直接把v[1]~v[9]分别与v[10]进行关联输出,累计用了20次操作。

5)   再复杂一点,假设A表id=3有2条记录,B表id=3有5条记录。首先读取v[0]发现是A表的记录,用了1次读取操作。然后再读取v[1]发现依然是A表的记录,累计用了2次读取操作。然后读取v[2]发现是B表的记录,此时v[0]和v[2]可以直接关联输出,累计用了3次操作。接下来v[0]可以依次和v[3]~v[6]进行关联输出,累计用了7次操作。接下来v[1]再依次和v[2]~v[6]进行关联输出,累计用了12次操作。

6)   把5的例子调过来,假设A表id=3有5条记录,B表id=3有2条记录。先读取v[0]发现是A表的记录,用了1次读取操作。然后再读取v[1]发现依然是A表的记录,累计用了2次读取操作。以此类推,读取到v[4]发现依然是A表的记录,累计用了5次读取操作。接下来读取v[5],发现是B表的记录,此时v[0]和v[5]可以直接关联输出,累计用了6次操作。然后v[0]和v[6]进行关联输出,累计用了7次操作。然后v[1]分别与v[5]、v[6]关联输出,累计用了9次操作。V[2] 分别与v[5]、v[6]关联输出,累计用了11次操作。以此类推,最后v[4] 分别与v[5]、v[6]关联输出,累计用了15次操作。

7)   额外提一下,当reduce检测A表的记录时,还要记录A表同一个key的记录的条数,当发现同一个key的记录个数超过hive.skewjoin.key的值(默认为1000000)时,会在reduce的日志中打印出该key,并标记为倾斜的关联键。

最终得出的结论是:写在关联左侧的表每有1条重复的关联键时底层就会多1次运算处理。

假设A表有一千万个id,平均每个id有3条重复值,那么把A表放在前面做关联就会多做三千万次的运算处理,这时候谁写在前谁写在后就看出性能的差别来了。

发布了136 篇原创文章 · 获赞 56 · 访问量 23万+

left join时产生数据倾斜怎么处理?

07-15

drop table if exists tableS; create table tableS as rcfile as select t.order_app_tp_cd as site_id, count(distinct t.order_id) cnt_order_all, count(distinct case when t.order_date = '${hivevar:statis_date}' then t.order_id else null end) as cnt_order, sum(case when t.order_date = '${hivevar:statis_date}' then t.pay_amt else 0.0 end) as amt_order, count(distinct case when t.pay_date = '${hivevar:statis_date}' and t.status = '30' then t.order_id else null end) as cnt_eff, sum(case when t.pay_date = '${hivevar:statis_date}' and t.status in ('30','50','60','70') then t.pay_amt else 0.0 end) as amt_eff, sum(case when t.pay_date = '${hivevar:statis_date}' and t.status in ('50','60','70') then t.pay_amt else 0.0 end) as amt_back, count(distinct case when t.pay_date = '${hivevar:statis_date}' and t.status = '30' then t.member_id else null end) as cnt_buyer, count(distinct case when c.member_id is not null and t.pay_date = '${hivevar:statis_date}' and t.status = '30' then t.member_id else null end) as cnt_buyer_new, count(distinct case when t.pay_date between date_sub(from_unixtime(to_unix_timestamp('${hivevar:statis_date}', 'yyyyMMdd'), 'yyyy-MM-dd'),14) and '${hivevar:statis_date}' and t.status='30' then t.order_id else null end) as cnt_eff_15, sum(case when t.pay_date between date_sub(from_unixtime(to_unix_timestamp('${hivevar:statis_date}', 'yyyyMMdd'), 'yyyy-MM-dd'),14) and '${hivevar:statis_date}' and t.status in('30','50','70') then t.pay_amt else 0.0 end) as amt_eff_15, count(distinct case when t.pay_date between date_sub(from_unixtime(to_unix_timestamp('${hivevar:statis_date}', 'yyyyMMdd'), 'yyyy-MM-dd'),14) and '${hivevar:statis_date}' and t.status='30' then t.member_id else null end) as cnt_buyer_15, count(distinct case when t.pay_date between date_sub(from_unixtime(to_unix_timestamp('${hivevar:statis_date}', 'yyyyMMdd'), 'yyyy-MM-dd'),29) and '${hivevar:statis_date}' and t.status='30' then t.order_id else null end) as cnt_eff_30, sum(case when t.pay_date between date_sub(from_unixtime(to_unix_timestamp('${hivevar:statis_date}', 'yyyyMMdd'), 'yyyy-MM-dd'),29) and '${hivevar:statis_date}' and t.status in('30','50','70') then t.pay_amt else 0.0 end) as amt_eff_30, count(distinct case when t.pay_date between date_sub(from_unixtime(to_unix_timestamp('${hivevar:statis_date}', 'yyyyMMdd'), 'yyyy-MM-dd'),29) and '${hivevar:statis_date}' and t.status='30' then t.member_id else null end) as cnt_buyer_30 from (select * from tableA t where statis_date between date_sub(from_unixtime(to_unix_timestamp('${hivevar:statis_date}', 'yyyyMMdd'), 'yyyy-MM-dd'),29) and '${hivevar:statis_date}' and order_apply_tp = 4 and order_dtl_apply_cd = '-1' and order_source = 'B2C' and gmv_Flg = 1 and chnl_cd = '50') t left join (select c1.member_id from tableB c1 where site_id not in ('PC','WAP','APP') and first_pay_time = '${hivevar:statis_date}' group by c1.member_id) c on t.member_id = c.member_id join (select name,code from tableC where statis_date between date_sub(from_unixtime(to_unix_timestamp('${hivevar:statis_date}', 'yyyyMMdd'), 'yyyy-MM-dd'),29) and '${hivevar:statis_date}' and status = 0) td on td.code = t.order_app_tp_cd group by t.order_app_tp_cd; 其中tableA是大表,tableB是小表,tableC是小表 运行后发现GC运行很频繁,应该是产生了数据倾斜?请问各位大佬怎么解决? 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览