我在 Hive 中加入两个大表(一个超过 10 亿行,一个大约 1 亿行(,如下所示:
create table joinedTable as select t1.id, ... from t1 join t2 ON (t1.id = t2.id);
我以相同的方式对两个表进行了分桶,按 id 聚类为每个表聚类到 100 个桶中,但查询仍然需要很长时间。
关于如何加快速度的任何建议?
当您通过联接键对数据进行存储时,您可以使用存储桶映射联接。为此,一个表中的存储桶数量必须是另一个表中存储桶数量的倍数。可以通过在查询之前执行set hive.optimize.bucketmapjoin=true;
来激活它。如果表不满足条件,Hive 将仅执行正常的内部联接。
如果两个表具有相同数量的存储桶,并且数据按存储桶键排序,则 Hive 可以执行更快的排序-合并联接。要激活它,您必须执行以下命令:
set hive.input.format=org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;
set hive.optimize.bucketmapjoin=true;
set hive.optimize.bucketmapjoin.sortedmerge=true;
您可以在 https://cwiki.apache.org/confluence/download/attachments/27362054/Hive+Summit+2011-join.pdf 下找到不同联接技术的一些可视化效果。
在我看来,答案比@Adrian朗格提供的要复杂一些。
首先,您必须了解 BucketJoin 和排序-合并存储桶连接 (SMBJ( 之间非常重要的区别:
要执行存储桶连接,"一个表中的存储桶数量必须是另一个表中存储桶数量的倍数",如前所述,此外hive.optimize.bucketmapjoin
必须设置为 true。
发出连接,如果发生上述条件,hive 会将其转换为存储桶连接,但请注意,hive 不会强制执行存储桶!这意味着创建已存储桶的表不足以将表实际存储桶到指定数量的存储桶中,因为 Hive 不会强制执行此操作,除非hive.enforce.bucketing
设置为 true(这意味着存储桶的数量实际上是由查询将数据插入表中的最后阶段的化简器数量设置的(。
从性能方面来看,请注意,当使用 bucketjoin 时,单个任务会在映射器访问它并执行连接之前将"较小"表读入分布式缓存 - 当您的表有 ~100m 行时,这个阶段可能会非常非常长且无效!
在病房之后,连接将与在减速器中完成的常规连接相同。
要执行 SMBJ,两个表必须具有完全相同数量的存储桶,在相同的列上,并且除了将hive.optimize.bucketmapjoin.sortedmerge
设置为 true 之外,还按这些列排序。
与前面的优化一样,Hive 不会强制执行存储桶和排序,而是假设你确保表实际被存储桶和排序(不仅通过定义,而且通过在插入数据时设置hive.enforce.sorting
或手动排序数据( - 这非常重要,因为它可能导致两种情况下的错误结果。
从性能方面来看,这种优化更有效,原因如下:
- 每个映射器读取两个存储桶,并且分布式缓存加载没有单个任务争用
- 正在执行的连接是合并排序连接,因为数据已经排序,效率更高。
请注意以下注意事项:
- 在这两种情况下
set hive.input.format=org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;
应该执行
在 - 这两种情况下,都应在查询中应用
/*+ MAPJOIN(b) */
(紧跟在select
之后,其中b
是较小的表( - 多少桶?
这应该从这个角度来看:应该严格考虑较大的表,因为它从这个方向产生更大的影响,然后配置将作为必须应用于较小的表。我认为根据经验,每个桶应该包含 1 到 3 个块,可能接近 2 个块。因此,如果您的块大小为 256MB,那么对我来说,在较大表中的每个存储桶中都有 ~512MB 的数据是合理的,因此这成为一个简单的除法问题。
此外,不要忘记,仅靠这些优化并不能始终保证更快的查询时间。
假设您选择执行 SMBJ,这会增加在运行连接之前对 2 个表进行排序的成本 - 因此,运行查询的次数越多,您为此排序阶段"支付"的费用就越少。
有时,一个简单的连接会带来最佳性能,而上述优化都无济于事,您必须在应用程序/逻辑级别或通过调整 MapReduce/Hive 设置(如内存使用/并行性等(来优化常规连接过程。
我不认为地图桶连接必须有"一个表中的桶量必须是另一个表中桶量的倍数"的标准,我们也可以有相同数量的桶。