1.1 Hadoop-impala十大优化之(2)—impala连接查询的性能优化
涉及连接操作的查询通常需要更多的调整,而不是仅指一个表的查询。从联接查询集合查询的结果集的最大大小是所有联接表中的行数的乘积。当加入数个表有数以百万计或数十亿的行,任何错过的机会过滤结果集,或其他低效的查询,可能导致一个操作,不完成在一个实际的时间,必须被取消。
调整Impala的最简单的方法连接查询是收集统计每个表参与联接的计算统计表,然后让Impala自动优化基于每个表的大小的查询,对每一列的不同值的数量,等等。计算统计表和连接优化了Impala1.2.2新特点。对于每个表的精确统计,在将数据加载到该表中后,发出计算统计报表,如果数据量大幅度的改变,加载数据,添加一个分区,等等。
如果没有可用的统计信息在连接查询所有表,或者如果Impala选择连接顺序,是不是最有效的,你可以重写自动连接顺序优化通过指定straight_join关键词后立即选择关键词。在这种情况下,Impala用出现的查询指导顺序处理。
当你使用straight_join技术,你必须手动而不是依靠Impala优化器查询表的连接。该优化使用复杂的技术来估计连接的每个阶段的结果集的大小。对于手动排序,采用这种启发式的方法开始,然后实验来微调顺序:
指定最大的表。这个表是由每个Impala节点从磁盘读取,其规模不是很大的内存使用情况的查询。
下一步,指定最小的表。第二、第三、等表的内容都在网络上传输。要将连接查询的每个后续阶段的结果集的大小减到最小。最有可能的方法是先加入一个小的表,这样结果集仍然很小,即使随后的较大的表被处理。
加入下一个最小的表,然后是下一个最小的,等等。
例如,如果你有表大,中,小,微小,逻辑连接顺序尝试将是大,微小,小,中。
术语“最大”和“最小”是指基于每个表中的行和列的数量的中间结果集的大小,是结果集的一部分。例如,如果你加入一个表的销售与另一个表的客户,查询可能会发现从100个不同的客户,共有5000采购的结果。在这种情况下,您将指定选择…从销售连接客户……,把客户放在右边,因为它在这个查询的上下文中是小的。
impala的查询计划选择不同的技术之间进行连接查询,根据表的绝对和相对大小。广播连接是默认的,在右边的表被认为是小于左边的表,它的内容被发送到查询中涉及的所有其他节点上。另一种技术被称为一个分区的连接(不涉及到一个分区表),这是更适合于大表的大小大致相等。使用这种技术,每个表的一部分被发送到适当的其他节点,其中的行的子集可以被并行处理。广播或分区连接的选择也取决于在联接中的所有表可用的统计数据,由计算统计报表所收集的数据中的所有表。
要查看哪些连接策略用于某个特定的查询,为查询发出一个解释语句。如果你发现一个查询使用广播连接,当你知道通过基准,一个分区的连接将是更有效的,或反之亦然,向查询添加一个提示,以指定使用精确的连接机制。在Impala的SELECT语句详见查询提示。
1.1.1 当统计数字不可用时如何处理
如果表或列统计信息不可用,在加入一些表,impala还是重新排序表使用信息是可用的。表统计放在连接顺序的左侧,在成本递减的顺序,基于整体规模和基数。没有统计的表被视为零大小,也就是说,它们总是放在连接顺序的右边。
1.1.2 straight_join连接顺序大于其他
如果Impala连接查询是没有效率的,过时的统计数据或意外的数据分布,你可以把impala从排序连接表采用straight_join关键词后立即选择关键词。的straight_join关键词关闭重新加入条款,impala是内部,并产生一个依赖于加入条款被有序的优化查询文本中的计划。在这种情况下,重写查询,以便最大的表在左边,其次是下一个最大的,等等,直到最小的表在右边。
在这个例子中,从大表的子查询产生一个非常小的结果集,但表可能仍然被视为最大的连接顺序放在第一。使用straight_join最后加入条款阻止最终的表被重新排序,使其在最右边的表的连接顺序。
selectstraight_join x from medium join small join (select * from big where c1 <10) as big
where medium.id = small.id and small.id =big.id;
1.1.3 连接顺序优化实例
这里的实际例子从侧面证明:调整Impala的最简单的方法连接查询是收集统计每个表参与联接的计算统计表。
这里有示例显示在表之间有10亿、2亿和100万行的连接。(在这种情况下,表没有分区和使用Parquet 格式。)小表包含从最大的一个数据子集,对加入的唯一ID列方便。最小的表只包含从其他表中的列的子集。
[localhost:21000]> create table big stored as parquet as select * from raw_data;
+----------------------------+
|summary |
+----------------------------+
| Inserted1000000000 row(s) |
+----------------------------+
|||Returned1 row(s) in 671.56s
[localhost:21000]> desc big;
+-----------+---------+---------+
| name | type | comment |
+-----------+---------+---------+
| id | int | |
| val | int | |
|zfill | string | |
| name | string | |
| assertion| boolean | |
+-----------+---------+---------+
Returned 5row(s) in 0.01s
[localhost:21000] > create table mediumstored as parquet as select * from big limit 200 * floor(1e6);
+---------------------------+
|summary |
+---------------------------+
| Inserted200000000 row(s) |
+---------------------------+
|Returned 1row(s) in 138.31s
[localhost:21000]> create table small stored as parquet as select id,val,name from big whereassertion = true limit 1 * floor(1e6);
+-------------------------+
|summary |
+-------------------------+
| Inserted1000000 row(s) |
+-------------------------+
|Returned 1row(s) in 6.32s
对于任何一种性能试验,使用EXPLAIN语句看到任何昂贵的查询将不进行实际运行,使冗长的解释计划包含更注重性能的细节:最有趣的计划线路以粗体突出显示,显示没有连接表统计,impala不能在处理各阶段涉及的行数的一个很好的估计,并可能坚持广播加入机制,将一张表完全复制到每个节点。
[localhost:21000]> set explain_level=verbose;
EXPLAIN_LEVELset to verbose
[localhost:21000]> explain select count(*) from big join medium where big.id = medium.id;
+----------------------------------------------------------+
| ExplainString |
+----------------------------------------------------------+
| EstimatedPer-Host Requirements: Memory=2.10GB VCores=2 |
| |
| PLANFRAGMENT 0 |
| PARTITION: UNPARTITIONED |
| |
| 6:AGGREGATE (merge finalize) |
| | output: SUM(COUNT(*)) |
| | cardinality: 1 |
| | per-host memory: unavailable |
| | tuple ids: 2 |
| | |
| 5:EXCHANGE |
| cardinality: 1 |
| per-host memory: unavailable |
| tuple ids: 2 |
| |
| PLANFRAGMENT 1 |
| PARTITION: RANDOM |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 5 |
| UNPARTITIONED |
| |
| 3:AGGREGATE |
| | output: COUNT(*) |
| | cardinality: 1 |
| | per-host memory: 10.00MB |
| | tuple ids: 2 |
| | |
| 2:HASH JOIN |
| | joinop: INNER JOIN (BROADCAST) |
| | hash predicates: |
| | big.id = medium.id |
| | cardinality: unavailable |
| | per-host memory: 2.00GB |
| | tuple ids: 0 1 |
| | |
| |----4:EXCHANGE |
| | cardinality: unavailable |
| | per-host memory: 0B |
| | tuple ids: 1 |
| | |
| 0:SCAN HDFS |
| table=join_order.big #partitions=1/1 size=23.12GB |
| table stats: unavailable |
| column stats: unavailable |
| cardinality: unavailable |
| per-host memory: 88.00MB |
| tuple ids: 0 |
| |
| PLANFRAGMENT 2 |
| PARTITION: RANDOM |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 4 |
| UNPARTITIONED |
| |
| 1:SCAN HDFS |
| table=join_order.medium #partitions=1/1 size=4.62GB |
| table stats: unavailable |
| column stats: unavailable |
| cardinality:unavailable |
| per-host memory: 88.00MB |
| tuple ids: 1 |
+----------------------------------------------------------+
Returned 64row(s) in 0.04s
收集所有表的统计数据是简单的,一个计算每表的计算统计语句:
[localhost:21000]> compute stats small;
+-----------------------------------------+
|summary |
+-----------------------------------------+
| Updated 1partition(s) and 3 column(s). |
+-----------------------------------------+
|Returned 1row(s) in 4.26s
[localhost:21000]> compute stats medium;
+-----------------------------------------+
|summary |
+-----------------------------------------+
| Updated 1partition(s) and 5 column(s). |
+-----------------------------------------+
|Returned 1row(s) in 42.11s
[localhost:21000]> compute stats big;
+-----------------------------------------+
|summary |
+-----------------------------------------+
| Updated 1partition(s) and 5 column(s). |
+-----------------------------------------+
|Returned 1row(s) in 165.44s
使用统计信息,Impala可以选择更有效的连接顺序而不是从左到右的顺序查询中的表,并可选择广播或分区的基础上加入整体尺寸和表中的行数的策略:
[localhost:21000]> explain select count(*) from medium join big where big.id = medium.id;
Query:explain select count(*) from medium join big where big.id = medium.id
+-----------------------------------------------------------+
| ExplainString |
+-----------------------------------------------------------+
| EstimatedPer-Host Requirements: Memory=937.23MB VCores=2 |
| |
| PLANFRAGMENT 0 |
| PARTITION: UNPARTITIONED |
| |
| 6:AGGREGATE (merge finalize) |
| | output: SUM(COUNT(*)) |
| | cardinality: 1 |
| | per-host memory: unavailable |
| | tuple ids: 2 |
| | |
| 5:EXCHANGE |
| cardinality: 1 |
| per-host memory: unavailable |
| tuple ids: 2 |
| |
| PLANFRAGMENT 1 |
| PARTITION: RANDOM |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 5 |
| UNPARTITIONED |
| |
| 3:AGGREGATE |
| | output: COUNT(*) |
| | cardinality: 1 |
| | per-host memory: 10.00MB |
| | tuple ids: 2 |
| | |
| 2:HASH JOIN |
| | join op: INNER JOIN (BROADCAST) |
| | hash predicates: |
| | big.id = medium.id |
| | cardinality: 1443004441 |
| | per-host memory: 839.23MB |
| | tuple ids: 1 0 |
| | |
| |----4:EXCHANGE |
| | cardinality: 200000000 |
| | per-host memory: 0B |
| | tuple ids: 0 |
| | |
| 1:SCAN HDFS |
| table=join_order.big #partitions=1/1size=23.12GB |
| table stats: 1000000000 rows total |
| column stats: all |
| cardinality: 1000000000 |
| per-host memory: 88.00MB |
| tuple ids: 1 |
| |
| PLANFRAGMENT 2 |
| PARTITION: RANDOM |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 4 |
| UNPARTITIONED |
| |
| 0:SCAN HDFS |
| table=join_order.medium #partitions=1/1size=4.62GB |
| table stats: 200000000 rows total |
| column stats: all |
| cardinality: 200000000 |
| per-host memory: 88.00MB |
| tuple ids: 0 |
+-----------------------------------------------------------+
Returned 64row(s) in 0.04s
[localhost:21000]> explain select count(*) from small join big where big.id = small.id;
Query:explain select count(*) from small join big where big.id = small.id
+-----------------------------------------------------------+
| ExplainString |
+-----------------------------------------------------------+
| EstimatedPer-Host Requirements: Memory=101.15MB VCores=2 |
| |
| PLANFRAGMENT 0 |
| PARTITION: UNPARTITIONED |
| |
| 6:AGGREGATE (merge finalize) |
| | output: SUM(COUNT(*)) |
| | cardinality: 1 |
| | per-host memory: unavailable |
| | tuple ids: 2 |
| | |
| 5:EXCHANGE |
| cardinality: 1 |
| per-host memory: unavailable |
| tuple ids: 2 |
| |
| PLANFRAGMENT 1 |
| PARTITION: RANDOM |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 5 |
| UNPARTITIONED |
| |
| 3:AGGREGATE |
| | output: COUNT(*) |
| | cardinality: 1 |
| | per-host memory: 10.00MB |
| | tuple ids: 2 |
| | |
| 2:HASH JOIN |
| | join op: INNER JOIN (BROADCAST) |
| | hash predicates: |
| | big.id = small.id |
| | cardinality: 1000000000 |
| | per-host memory: 3.15MB |
| | tuple ids: 1 0 |
| | |
| |----4:EXCHANGE |
| | cardinality: 1000000 |
| | per-host memory: 0B |
| | tuple ids: 0 |
| | |
| 1:SCAN HDFS |
| table=join_order.big #partitions=1/1size=23.12GB |
| table stats: 1000000000 rows total |
| column stats: all |
| cardinality: 1000000000 |
| per-host memory: 88.00MB |
| tuple ids: 1 |
| |
| PLANFRAGMENT 2 |
| PARTITION: RANDOM |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 4 |
| UNPARTITIONED |
| |
| 0:SCAN HDFS |
| table=join_order.small #partitions=1/1size=17.93MB |
| table stats: 1000000 rows total |
| column stats: all |
| cardinality: 1000000 |
| per-host memory: 32.00MB |
| tuple ids: 0 |
+-----------------------------------------------------------+
Returned 64row(s) in 0.03s
当这些查询实际上是运行时,无论查询文本中的表顺序,执行时间都是相对一致的。这里是一个例子使用独特的ID列包含重复值的列的值:
[localhost:21000]> select count(*) from big join small on (big.id = small.id);
Query:select count(*) from big join small on (big.id = small.id)
+----------+
| count(*)|
+----------+
|1000000 |
+----------+
|Returned 1row(s) in 21.68s
[localhost:21000]> select count(*) from small join big on (big.id = small.id);
Query:select count(*) from small join big on (big.id = small.id)
+----------+
| count(*)|
+----------+
|1000000 |
+----------+
|Returned 1row(s) in 20.45s
[localhost:21000]> select count(*) from big join small on (big.val = small.val);
+------------+
|count(*) |
+------------+
|2000948962 |
+------------+
|Returned 1row(s) in 108.85s
[localhost:21000]> select count(*) from small join big on (big.val = small.val);
+------------+
|count(*) |
+------------+
|2000948962 |
+------------+
|Returned 1row(s) in 100.76s
注意:当检查联接查询的性能和联接顺序优化的有效性时,请确保查询包含足够的数据和群集资源,以查看基于查询计划的差异。例如,一个只有几兆的单个数据文件驻留在一个单一的HDFS块,在一个单一的节点处理。同样,如果使用单个节点或两个节点群集,广播或分区的联接策略的效率可能不会有太大的差异。