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

南墨2年前技术文章852

经常看到一些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表放在前面做关联就会多做三千万次的运算处理,这时候谁写在前谁写在后就看出性能的差别来了。


来源:Hive中小表与大表关联(join)的性能分析_hive 用小表做主表跑数更快吗_涤生大数据的博客-CSDN博客

相关文章

EMR yarn集成Cgroup

EMR yarn集成Cgroup

CGroups(Control Groups)是Linux内核的一个资源隔离功能,限制/隔离/统计进程的资源使用,包括CPU/内存/磁盘IO/网络等资源。YARN中也集成了CGroups的功能,使得N...

Linux下的IO统计工具:iostat,iotop

Linux下的IO统计工具:iostat,iotop

iostat      监视系统输入输出设备和CPU的使用情况。iostat命令 被用于监视系统输入输出设备和CPU的使用情况。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。同vmst...

Linux解锁线程基本概念和线程控制,步入多线程学习的大门(2)

Linux解锁线程基本概念和线程控制,步入多线程学习的大门(2)

2.4.线程等待:为什么需要线程等待?已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。不然也会造成内存泄露问题!创建新的线程不会复用刚才退出线程的地址空间。主线程退出 == 进程退出 ==...

ES运维(一)底层数据存储原理

ES运维(一)底层数据存储原理

1、ES底层数据存储原理架构图Segment工作流程:A、 新的文档在内存中组织B、 每隔一段时间,buffer将会被提交:生成一个新的segment(一个额外的新的倒序索引)并被写到磁盘,同时一个新...

scylladb通过扩缩容节点迁移数据

环境: Scyllsdb版本:4.2一、上线新节点1、确认集群状态和检查配置· 首先确认集群各节点状态是Up Normal (UN),[root@172-16-121-153 scylla]# nod...

Redis Sentinel与Cluster安装部署(一)

Redis Sentinel与Cluster安装部署(一)

1、部署规划版本redis5.0.8目录规划软件安装目录/usr/local/redis5可执行文件目录/usr/local/redis5/bin数据目录/usr/local/redis5/dada/...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。