Impala教程

本节包括演示如何在安装软件后开始使用Impala的教程场景。它侧重于加载数据的技术,因为一旦您在表中有一些数据并且可以查询该数据,您就可以快速进入更高级的Impala功能。
注意:
在可行的情况下,教程将您从“零接地”带到拥有所需的Impala表和数据。在某些情况下,您可能需要从外部源下载其他文件,设置其他软件组件,修改命令或脚本以适合您自己的配置,或者替换您自己的示例数据。
在尝试这些教程课程之前,请使用以下过程之一安装Impala:

  • 如果您已经设置了一些Apache Hadoop环境并且只需要向其添加Impala,请按照第23页的“安装Impala”中所述的安装过程进行操作。如果您尚未配置Hive,请确保还安装Hive Metastore服务。

入门教程

这些教程演示了使用Impala的基础知识。它们适用于首次使用的用户,以及在任何新群集上尝试Impala以确保主要组件正常工作。

探索新的Impala实例

本教程演示了如何在不熟悉(可能为空)的Impala实例的表和数据库中查找方法。

当您第一次连接到Impala实例时,可以使用SHOW DATABASES和SHOW TABLES语句来查看最常见的对象类型。另外,调用version()函数来确认您正在运行的Impala版本;在查阅文档和处理支持问题时,版本号很重要。

完全空的Impala实例不包含表,但仍有两个数据库:

  • default,如果未指定任何其他数据库,则会创建新表。
  • _impala_builtins,一个用于保存所有内置函数的系统数据库。

以下示例显示如何查看可用数据库以及每个数据库中的表。 如果数据库或表的列表很长,则可以使用通配符表示法根据其名称查找特定数据库或表。

[root@slave2 workspace]# sudo -u hdfs impala-shell --quiet
[slave2:21000] default> select version();
+-----------------------------------------------------------------------------------------+
| version()                                                                               |
+-----------------------------------------------------------------------------------------+
| impalad version 3.0.0-cdh6.0.1 RELEASE (build 9a74a5053de5f7b8dd983802e6d75e58d31472db) |
| Built on Wed Sep 19 11:27:37 PDT 2018                                                   |
+-----------------------------------------------------------------------------------------+
[slave2:21000] default> show databases;
+------------------+----------------------------------------------+
| name             | comment                                      |
+------------------+----------------------------------------------+
| _impala_builtins | System database for Impala builtin functions |
| default          | Default Hive database                        |
| sunchengquan     |                                              |
| test             |                                              |
| var              |                                              |
| var1             |                                              |
| var2             |                                              |
| var_kudu         |                                              |
+------------------+----------------------------------------------+

[slave2:21000] default> select current_database();
+--------------------+
| current_database() |
+--------------------+
| default            |
+--------------------+
[slave2:21000] default> show tables;
+------+
| name |
+------+
| yy   |
| yy2  |
+------+

[slave2:21000] default> show tables in var_kudu;
+----------------+
| name           |
+----------------+
| cancer_info    |
| gene           |
| genetic_info   |
| lineage        |
| sample         |
| snv_indel_bank |
| snv_indel_file |
+----------------+

[slave2:21000] default> show tables in var_kudu like 'gene*';
+--------------+
| name         |
+--------------+
| gene         |
| genetic_info |
+--------------+

一旦知道哪些表和数据库可用,就可以使用USE语句进入数据库。 要了解每个表的结构,请使用DESCRIBE命令。 进入数据库后,您可以发出对特定表进行操作的语句,如INSERT和SELECT。

以下示例探讨了一个名为TPC的数据库,其名称是我们在前面的示例中学到的。 它显示了如何根据搜索字符串过滤数据库中的表名,检查表的列,并运行查询以检查表数据的特征。 例如,对于不熟悉的表,您可能想知道行数,列的不同值的数量以及其他属性,例如列是否包含任何NULL值。 从表中采样实际数据值时,如果表包含的行或值超出预期,请使用LIMIT子句以避免过多输出。

[slave2:21000] var_kudu> use var_kudu;
[slave2:21000] var_kudu> show tables;
+----------------+
| name           |
+----------------+
| cancer_info    |
| gene           |
| genetic_info   |
| lineage        |
| sample         |
| snv_indel_bank |
| snv_indel_file |
+----------------+
[slave2:21000] var_kudu> describe gene;
+------------------------------------+--------+---------+-------------+----------+---------------+---------------+---------------------+------------+
| name                               | type   | comment | primary_key | nullable | default_value | encoding      | compression         | block_size |
+------------------------------------+--------+---------+-------------+----------+---------------+---------------+---------------------+------------+
| gene_id                            | string |         | true        | false    |               | AUTO_ENCODING | DEFAULT_COMPRESSION | 0          |
| ensembl_id                         | string |         | false       | true     |               | AUTO_ENCODING | DEFAULT_COMPRESSION | 0          |
| symbol_name                        | string |         | false       | true     |               | AUTO_ENCODING | DEFAULT_COMPRESSION | 0          |
| synonyms                           | string |         | false       | true     |               | AUTO_ENCODING | DEFAULT_COMPRESSION | 0          |
| chromosome                         | string |         | false       | true     |               | AUTO_ENCODING | DEFAULT_COMPRESSION | 0          |
| map_location                       | string |         | false       | true     |               | AUTO_ENCODING | DEFAULT_COMPRESSION | 0          |
| description                        | string |         | false       | true     |               | AUTO_ENCODING | DEFAULT_COMPRESSION | 0          |
| type_of_gene                       | string |         | false       | true     |               | AUTO_ENCODING | DEFAULT_COMPRESSION | 0          |
| symbol_from_nomenclature_authority | string |         | false       | true     |               | AUTO_ENCODING | DEFAULT_COMPRESSION | 0          |
+------------------------------------+--------+---------+-------------+----------+---------------+---------------+---------------------+------------+

[slave2:21000] var_kudu> select count(*) from gene ;
+----------+
| count(*) |
+----------+
| 61389    |
+----------+
[slave2:21000] var_kudu> select count(distinct chromosome) from gene ;
+----------------------------+
| count(distinct chromosome) |
+----------------------------+
| 29                         |
+----------------------------+

[slave2:21000] var_kudu> select distinct chromosome from gene limit 3;
+------------+
| chromosome |
+------------+
| 20         |
| 9          |
| X|Y        |
+------------+


可以使用CREATE DATABASE和CREATE TABLE等语句来设置自己的数据库对象。

以下示例演示如何创建包含新表的新数据库。 尽管最后一个示例在TPC数据库中结束,但新的EXPERIMENTS数据库并未嵌套在TPC中; 所有数据库都排列在一个顶级列表中。

create database experiments;
create database tpc;
show databases;

[slave2:21000] var_kudu> show databases like 'var*';
+----------+---------+
| name     | comment |
+----------+---------+
| var      |         |
| var1     |         |
| var2     |         |
| var_kudu |         |
+----------+---------+

以下示例创建一个新表T1。 为了说明一个常见的错误,它在错误的数据库(前一个示例结束的TPC数据库)中创建了这个表。 ALTER TABLE语句允许您将表移动到目标数据库EXPERIMENTS,作为重命名操作的一部分。 切换到新数据库总是需要USE语句,current_database()函数确认会话所在的数据库,以避免这些错误。

[slave2:21000] test> use tpc;
[slave2:21000] tpc> create table t1 (x int);
+-------------------------+
| summary                 |
+-------------------------+
| Table has been created. |
+-------------------------+
[slave2:21000] tpc> show tables;
+------+
| name |
+------+
| t1   |
+------+
[slave2:21000] tpc> select current_database();
+--------------------+
| current_database() |
+--------------------+
| tpc                |
+--------------------+

[slave2:21000] tpc> alter table t1 rename to experiments.t1;
+--------------------------+
| summary                  |
+--------------------------+
| Renaming was successful. |
+--------------------------+
[slave2:21000] tpc> use experiments;
[slave2:21000] experiments> show tables;
+------+
| name |
+------+
| t1   |
+------+
[slave2:21000] experiments> select current_database();
+--------------------+
| current_database() |
+--------------------+
| experiments        |
+--------------------+

对于表的初始实验,您可以使用只有几列和几行的文本格式数据文件。

注意:当您进入更现实的场景时,您将使用更多精心设计的表,其中包含许多列,分区等功能以及Parquet等文件格式。 处理实际数据卷时,您将使用LOAD DATA或INSERT … SELECT语句引入数据,以便同时对数百万或数十亿行进行操作。

以下示例设置了几个包含几行的简单表,并执行涉及排序,聚合函数和连接的查询。


[slave2:21000] experiments> insert into t1 values (1), (3), (2), (4);
[slave2:21000] experiments> select x from t1 order by x desc;
[slave2:21000] experiments> select x from t1 ;
+---+
| x |
+---+
| 1 |
| 3 |
| 2 |
| 4 |
+---+

[slave2:21000] experiments> select min(x), max(x), sum(x), avg(x) from t1;
+--------+--------+--------+--------+
| min(x) | max(x) | sum(x) | avg(x) |
+--------+--------+--------+--------+
| 1      | 4      | 10     | 2.5    |
+--------+--------+--------+--------+

[slave2:21000] experiments> create table t2 (id int, word string);
+-------------------------+
| summary                 |
+-------------------------+
| Table has been created. |
+-------------------------+
[slave2:21000] experiments> insert into t2 values (1, "one"), (3, "three"), (5,
                          >  'five');
[slave2:21000] experiments> select word from t1 join t2 on (t1.x = t2.id);
+-------+
| word  |
+-------+
| one   |
| three |
+-------+

完成本教程后,您现在应该知道:

  • 如何判断系统上运行的Impala版本。
  • 如何在Impala实例中查找数据库的名称,显示完整列表或搜索特定列表名。
  • 如何在Impala数据库中查找表的名称,显示完整列表或搜索特定列表名。
  • 如何在数据库之间切换并检查您当前所在的数据库。
  • 如何学习表的列名和类型。
  • 如何创建数据库和表,插入少量测试数据以及运行简单查询。

从本地文件加载CSV数据

此方案说明了如何创建一些非常小的表,适合首次使用Impala SQL功能的用户。 TAB1和TAB2加载来自HDFS中文件的数据。数据子集从TAB1复制到TAB3。

使用要查询的数据填充HDFS。要开始此过程,请在HDFS中的用户目录下创建一个或多个新子目录。每个表的数据都位于一个单独的子目录中。在适当的地方用自己的用户名替换用户名。此示例将-p选项与mkdir操作一起使用,以创建任何必需的父目录(如果它们尚不存在)。

[root@slave2 workspace]# whoami
root
[root@slave2 workspace]# hdfs dfs -ls /user
Found 8 items
drwx------   - hdfs   supergroup          0 2019-06-17 07:18 /user/hdfs
drwxrwxrwx   - mapred hadoop              0 2019-05-30 13:51 /user/history
drwxrwxr-t   - hive   hive                0 2019-05-30 13:51 /user/hive
drwxrwxr-x   - hue    hue                 0 2019-05-30 13:56 /user/hue
drwxrwxr-x   - impala impala              0 2019-06-13 22:59 /user/impala
drwxrwxr-x   - oozie  oozie               0 2019-05-30 13:50 /user/oozie
drwxr-x--x   - spark  spark               0 2019-05-30 22:43 /user/spark
drwxr-xr-x   - hdfs   supergroup          0 2019-05-30 13:50 /user/yarn
[root@slave2 workspace]# sudo -u hdfs hdfs dfs -mkdir -p /user/root/sample_data/tab1 /user/root/sample_data/tab2

将以下内容复制到本地文件系统中的.csv文件:tab1.csv:

1,true,123.123,2012-10-24 08:55:00
2,false,1243.5,2012-10-25 13:40:00
3,false,24453.325,2008-08-22 09:33:21.123
4,false,243423.325,2007-05-12 22:32:21.33454
5,true,243.325,1953-04-22 09:11:33

tab2.csv:

1,true,12789.123
2,false,1243.5
3,false,24453.325
4,false,2423.3254
5,true,243.325
60,false,243565423.325
70,true,243.325
80,false,243423.325
90,true,243.325

使用以下命令将每个.csv文件放入单独的HDFS目录中,这些命令使用Impala Demo VM中可用的路径:

[root@slave2 workspace]# sudo -u hdfs hdfs dfs -put ./tab1.csv /user/root/sample_data/tab1
[root@slave2 workspace]# sudo -u hdfs hdfs dfs -ls /user/root/sample_data/tab1
Found 1 items
-rw-r--r--   2 hdfs supergroup        192 2019-06-18 17:27 /user/root/sample_data/tab1/tab1.csv
[root@slave2 workspace]# sudo -u hdfs hdfs dfs -put ./tab2.csv /user/root/sample_data/tab2
[root@slave2 workspace]# sudo -u hdfs hdfs dfs -ls /user/root/sample_data/tab2
Found 1 items
-rw-r--r--   2 hdfs supergroup        158 2019-06-18 17:28 /user/root/sample_data/tab2/tab2.csv

每个数据文件的名称都不重要。事实上,当Impala第一次检查数据目录的内容时,它会认为目录中的所有文件都构成了表的数据,无论文件有多少或文件命名的内容。

要了解您自己的HDFS文件系统中可用的路径以及各种目录和文件的权限,请发出hdfs dfs -ls /并按照树的方式对各种目录执行-ls操作。
使用impala-shell命令以交互方式或通过SQL脚本创建表。

以下示例显示了创建三个表。对于每个表,该示例显示了创建具有各种属性(如布尔或整数类型)的列。该示例还包括提供有关数据格式化方式的信息的命令,例如以逗号结尾的行,这在从.csv文件导入数据的情况下是有意义的。我们已经在HDFS目录树中包含包含数据的.csv文件,我们指定包含相应.csv文件的目录的位置。 Impala会考虑该目录中所有文件的所有数据来表示该表的数据。

DROP TABLE IF EXISTS tab1;
-- The EXTERNAL clause means the data is located outside the central location
-- for Impala data files and is preserved when the associated Impala table is dropped.
-- We expect the data to already exist in the directory specified by the LOCATION clause.

CREATE EXTERNAL TABLE tab1
(
id INT,
   col_1 BOOLEAN,
   col_2 DOUBLE,
   col_3 TIMESTAMP
 ) 
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION '/user/root/sample_data/tab1';




DROP TABLE IF EXISTS tab2;
-- TAB2 is an external table, similar to TAB1.
CREATE EXTERNAL TABLE tab2
(
   id INT,
   col_1 BOOLEAN,
   col_2 DOUBLE 
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION '/user/root/sample_data/tab2';



DROP TABLE IF EXISTS tab3;
-- Leaving out the EXTERNAL clause means the data will be managed
-- in the central Impala data directory tree. Rather than reading
-- existing data files when the table is created, we load the
-- data after creating the table.
CREATE TABLE tab3
(
   id INT,
   col_1 BOOLEAN,
   col_2 DOUBLE,
   month INT,
   day INT
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

注意:成功完成这些CREATE TABLE语句是确认使用Hive Metastore和HDFS权限正确配置所有内容的重要验证步骤。如果在CREATE TABLE语句期间收到任何错误:

  • 确保按照安装Impala(第23页)中的安装说明进行操作。
  • 确保hive.metastore.warehouse.dir属性指向Impala可以写入的目录。
    所有权应该是hive:hive,impala用户也应该是hive组的成员。将Impala表指向现有数据文件

从存在的文件中指定Impala表。

设置Impala访问数据的便捷方法是使用外部表,其中数据已存在于一组HDFS文件中,您只需将Impala表指向包含这些文件的目录即可。例如,您可以在impala-shell a * .sql文件中运行,其内容类似于以下内容,以创建访问Hive使用的现有数据

以下示例设置了2个表,引用了Impala样本TPC-DS工具包中的路径和样本数据。由于历史原因,数据实际驻留在/ user / hive下的HDFS目录树中,尽管这些特定数据完全由Impala而不是Hive管理。当我们创建外部表时,我们指定包含一个或多个数据文件的目录,Impala查询该目录中所有文件的组合内容。以下是我们检查HDFS文件系统中的目录和文件的方法:


$ cd /data/workspace
$ ./tpcds-setup.sh
... Downloads and unzips the kit, builds the data and loads it into HDFS ...
$ hdfs dfs -ls /user/hive/tpcds/customer
Found 1 items

[root@slave2 workspace]# sudo -u hdfs hdfs dfs -cat /user/hive/tpcds/customer/customer.dat | more
1|AAAAAAAABAAAAAAA|980124|7135|32946|2452238|2452208|Mr.|Javier|Lewis|Y|9|12|1936|CHILE||[email protected]|2452508|
2|AAAAAAAACAAAAAAA|819667|1461|31655|2452318|2452288|Dr.|Amy|Moses|Y|9|4|1966|TOGO||[email protected]|2452318|
3|AAAAAAAADAAAAAAA|1473522|6247|48572|2449130|2449100|Miss|Latisha|Hamilton|N|18|9|1979|NIUE||[email protected]|
4|AAAAAAAAEAAAAAAA|1703214|3986|39558|2450030|2450000|Dr.|Michael|White|N|7|6|1983|MEXICO||[email protected]|2452361|
5|AAAAAAAAFAAAAAAA|953372|4470|36368|2449438|2449408|Sir|Robert|Moran|N|8|5|1956|FIJI||[email protected]|245246|


这是一个SQL脚本,用于设置指向HDFS中某些数据文件的Impala表。 (VM中的脚本通过Hive设置这样的表;为了演示而忽略这些表。)将以下内容保存为customer_setup.sql:

--
-- store_sales fact table and surrounding dimension tables only
--
create database tpcds;
use tpcds;
drop table if exists customer;
create external table customer
(
    c_customer_sk             int,
    c_customer_id             string,
    c_current_cdemo_sk        int,
    c_current_hdemo_sk        int,
    c_current_addr_sk         int,
    c_first_shipto_date_sk    int,
    c_first_sales_date_sk     int,
    c_salutation              string,
    c_first_name              string,
    c_last_name               string,
    c_preferred_cust_flag     string,
    c_birth_day               int,
    c_birth_month             int,
    c_birth_year              int,
    c_birth_country           string,
    c_login                   string,
    c_email_address           string,
    c_last_review_date        string
)
row format delimited fields terminated by '|'
location '/user/hive/tpcds/customer';

我们将使用如下命令运行此脚本:

sudo -u hdfs impala-shell -i slave2:21000 -f customer_setup.sql

描述Impala表

现在您已经更新了Impala缓存的数据库元数据,您可以确认Impala可以访问预期的表并检查其中一个表的属性。 我们在名为default的数据库中创建了这些表。 如果表位于默认数据库以外的数据库中,我们将发出命令use db_name在检查或查询其表之前切换到该数据库。 我们还可以通过添加数据库名称来限定表的名称,例如default.customer和default.customer_name。

[slave2:21000] tpcds> show databases;
Query: show databases
+------------------+----------------------------------------------+
| name             | comment                                      |
+------------------+----------------------------------------------+
| _impala_builtins | System database for Impala builtin functions |
| default          | Default Hive database                        |
| experiments      |                                              |
| sunchengquan     |                                              |
| test             |                                              |
| tpc              |                                              |
| tpcds            |                                              |
| var              |                                              |
| var1             |                                              |
| var2             |                                              |
| var_kudu         |                                              |
+------------------+----------------------------------------------+
Fetched 11 row(s) in 0.00s


[slave2:21000] tpcds> show tables;
Query: show tables
+----------+
| name     |
+----------+
| customer |
+----------+
Fetched 1 row(s) in 0.00s

[slave2:21000] tpcds> describe customer
                    > ;
Query: describe customer
+------------------------+--------+---------+
| name                   | type   | comment |
+------------------------+--------+---------+
| c_customer_sk          | int    |         |
| c_customer_id          | string |         |
| c_current_cdemo_sk     | int    |         |
| c_current_hdemo_sk     | int    |         |
| c_current_addr_sk      | int    |         |
| c_first_shipto_date_sk | int    |         |
| c_first_sales_date_sk  | int    |         |
| c_salutation           | string |         |
| c_first_name           | string |         |
| c_last_name            | string |         |
| c_preferred_cust_flag  | string |         |
| c_birth_day            | int    |         |
| c_birth_month          | int    |         |
| c_birth_year           | int    |         |
| c_birth_country        | string |         |
| c_login                | string |         |
| c_email_address        | string |         |
| c_last_review_date     | string |         |
+------------------------+--------+---------+
Fetched 18 row(s) in 0.01s

查询Impala表

您可以查询表中包含的数据。 Impala根据您的配置在单个节点或多个节点上协调查询执行,而无需运行MapReduce作业执行中间处理的开销。

在Impala上执行查询的方法有很多种:

  • 在交互模式下使用impala-shell命令:
$ impala-shell -i impala-host
Connected to localhost:21000
[impala-host:21000] > select count(*) from customer_address;
50000
Returned 1 row(s) in 0.37s
  • 传递文件中包含的一组命令:
$ impala-shell -i impala-host -f myquery.sql
Connected to localhost:21000
50000
Returned 1 row(s) in 0.19s
  • 将单个命令传递给impala-shell命令。 执行查询,返回结果,并退出shell。 确保引用命令,最好使用单引号,以避免shell扩展*等字符。
$ impala-shell -i impala-host -q 'select count(*) from customer_address'
Connected to localhost:21000
50000
Returned 1 row(s) in 0.29s

数据加载和查询示例

本节介绍如何创建一些示例表并将数据加载到这些表中。 然后可以使用Impala shell查询这些表。

加载数据中

加载数据涉及:

  • 建立数据集。 以下示例使用.csv文件。
  • 创建要加载数据的表。
  • 将数据加载到您创建的表中。

示例查询

要运行这些示例查询,请创建SQL查询文件query.sql,将每个查询复制并粘贴到查询文件中,然后使用shell运行查询文件。 例如,要在impala-host上运行query.sql,您可以使用以下命令:

impala-shell.sh -i impala-host -f query.sql

下面的示例和结果假设您已将示例数据加载到表中,如上所述。

示例:检查表的内容

让我们首先验证表是否包含我们期望的数据。 因为Impala经常处理包含数百或数十亿行的表,所以在检查未知大小的表时,请包含LIMIT子句以避免大量不必要的输出,如最终查询中所示。 (如果交互式查询开始显示意外的数据量,请在impala- shell中按Ctrl-C取消查询。)

SELECT * FROM tab1;
SELECT * FROM tab2;
[slave2:21000] experiments> SELECT * FROM tab2 LIMIT 5;
Query: SELECT * FROM tab2 LIMIT 5
Query submitted at: 2019-06-19 11:05:50 (Coordinator: http://slave2:25000)
Query progress can be monitored at: http://slave2:25000/query_plan?query_id=8248a02ff43aaabb:66d695b200000000
+----+-------+-----------+
| id | col_1 | col_2     |
+----+-------+-----------+
| 1  | true  | 12789.123 |
| 2  | false | 1243.5    |
| 3  | false | 24453.325 |
| 4  | false | 2423.3254 |
| 5  | true  | 243.325   |
+----+-------+-----------+
Fetched 5 row(s) in 3.77s

示例: Aggregate and Join

SELECT tab1.col_1, MAX(tab2.col_2), MIN(tab2.col_2)
FROM tab2 JOIN tab1 USING (id)
GROUP BY col_1 ORDER BY 1 LIMIT 5;


+-------+-----------------+-----------------+
| col_1 | max(tab2.col_2) | min(tab2.col_2) |
+-------+-----------------+-----------------+
| false | 24453.325       | 1243.5          |
| true  | 12789.123       | 243.325         |
+-------+-----------------+-----------------+

示例:子查询,聚合和连接

SELECT tab2.*
FROM tab2,
    (SELECT tab1.col_1, MAX(tab2.col_2) AS max_col2
     FROM tab2, tab1
     WHERE tab1.id = tab2.id
     GROUP BY col_1) subquery1
WHERE subquery1.max_col2 = tab2.col_2;


+----+-------+-----------+
| id | col_1 | col_2     |
+----+-------+-----------+
| 1  | true  | 12789.123 |
| 3  | false | 24453.325 |
+----+-------+-----------+

示例:插入查询

INSERT OVERWRITE TABLE tab3
SELECT id, col_1, col_2, MONTH(col_3), DAYOFMONTH(col_3)
FROM tab1 WHERE YEAR(col_3) = 2012;

SELECT * FROM tab3;

+----+-------+---------+-------+-----+
| id | col_1 | col_2   | month | day |
+----+-------+---------+-------+-----+
| 1  | true  | 123.123 | 10    | 24  |
| 2  | false | 1243.5  | 10    | 25  |
+----+-------+---------+-------+-----+

进阶教程

这些教程将指导您完成高级方案或专用功能。

将外部分区表附加到HDFS目录结构

本教程介绍如何在HDFS中设置目录树,将数据文件放入最低级别的子目录,然后使用Impala外部表从其原始位置查询数据文件。

本教程使用包含Web日志数据的表,其中包含年,月,日和主机的单独子目录。为简单起见,我们使用少量CSV数据,将相同数据加载到每个分区。

首先,我们为CSV数据创建一个Impala分区表,并查看底层HDFS目录结构,以了解在HDFS中重新创建的目录结构。列field1,field2和field3对应于CSV数据文件的内容。年,月,日和主机列都表示为表结构中的子目录,并且不是CSV文件的一部分。我们对每个列都使用STRING,这样我们就可以生成一致的子目录名称,并且前导零的长度一致。

create database external_partitions;
use external_partitions;
create table logs (field1 string, field2 string, field3 string)
  partitioned by (year string, month string , day string, host string)
  row format delimited fields terminated by ',';
insert into logs partition (year="2013", month="07", day="28", host="host1") values ("foo","foo","foo");
insert into logs partition (year="2013", month="07", day="28", host="host2") values ("foo","foo","foo");
insert into logs partition (year="2013", month="07", day="29", host="host1") values ("foo","foo","foo");
insert into logs partition (year="2013", month="07", day="29", host="host2") values ("foo","foo","foo");
insert into logs partition (year="2013", month="08", day="01", host="host1") values ("foo","foo","foo");

回到Linux shell,我们检查HDFS目录结构。 (您的Impala数据目录可能位于不同的位置;由于历史原因,它有时位于HDFS路径/ user / hive / warehouse下。)我们使用hdfs dfs -ls命令检查与每个分区列对应的嵌套子目录, 每个级别的单独子目录(名称中带有=)表示每个分区列的不同值。 当我们到达最低级别的子目录时,我们使用hdfs dfs -cat命令检查数据文件,并查看Impala中INSERT语句生成的CSV格式数据。

[root@slave2 workspace]# hdfs dfs -ls /user/hive/warehouse/external_partitions.db/logs
Found 2 items
drwxrwxrwx   - impala hive          0 2019-06-19 11:22 /user/hive/warehouse/external_partitions.db/logs/_impala_insert_staging
drwxr-xr-x   - impala hive          0 2019-06-19 11:22 /user/hive/warehouse/external_partitions.db/logs/year=2013
[root@slave2 workspace]# hdfs dfs -ls /user/hive/warehouse/external_partitions.db/logs/year=2013
Found 2 items
drwxr-xr-x   - impala hive          0 2019-06-19 11:22 /user/hive/warehouse/external_partitions.db/logs/year=2013/month=07
drwxr-xr-x   - impala hive          0 2019-06-19 11:22 /user/hive/warehouse/external_partitions.db/logs/year=2013/month=08

[root@slave2 workspace]# hdfs dfs -ls /user/hive/warehouse/external_partitions.db/logs/year=2013/month=07/day=29/host=host2
Found 1 items
-rw-r--r--   2 impala hive         12 2019-06-19 11:22 /user/hive/warehouse/external_partitions.db/logs/year=2013/month=07/day=29/host=host2/f44719c15b198e92-d00cee8d00000000_237500100_data.0.


[root@slave2 workspace]# hdfs dfs -cat  /user/hive/warehouse/external_partitions.db/logs/year=2013/month=07/day=29/host=host2/f44719c15b198e92-d00cee8d00000000_237500100_data.0.
foo,foo,foo

仍然在Linux shell中,我们使用hdfs dfs -mkdir在Impala控制的HDFS目录树之外创建几个数据目录(在本例中为/ user / impala / warehouse,在您的情况下可能不同)。 根据您的配置,您可能需要以具有写入此HDFS目录树的权限的用户身份登录; 对于
例如,此处显示的命令在以hdfs用户身份登录时运行。


$ hdfs dfs -mkdir -p /user/impala/data/logs/year=2013/month=07/day=28/host=host1
$ hdfs dfs -mkdir -p /user/impala/data/logs/year=2013/month=07/day=28/host=host2
$ hdfs dfs -mkdir -p /user/impala/data/logs/year=2013/month=07/day=28/host=host1
$ hdfs dfs -mkdir -p /user/impala/data/logs/year=2013/month=07/day=29/host=host1
$ hdfs dfs -mkdir -p /user/impala/data/logs/year=2013/month=08/day=01/host=host1

我们创建一个小的CSV文件,其值与前面使用的INSERT语句不同,并在我们将用作Impala分区的每个子目录中放置一个副本。


$ cat >dummy_log_data
bar,baz,bletch
$ hdfs dfs -mkdir -p /user/impala/data/external_partitions/year=2013/month=08/day=01/host=host1
$ hdfs dfs -mkdir -p /user/impala/data/external_partitions/year=2013/month=07/day=28/host=host1
$ hdfs dfs -mkdir -p /user/impala/data/external_partitions/year=2013/month=07/day=28/host=host2
$ hdfs dfs -mkdir -p /user/impala/data/external_partitions/year=2013/month=07/day=29/host=host1
$ hdfs dfs -put dummy_log_data /user/impala/data/logs/year=2013/month=07/day=28/host=host1
$ hdfs dfs -put dummy_log_data /user/impala/data/logs/year=2013/month=07/day=28/host=host2
$ hdfs dfs -put dummy_log_data /user/impala/data/logs/year=2013/month=07/day=29/host=host1
$ hdfs dfs -put dummy_log_data /user/impala/data/logs/year=2013/month=08/day=01/host=host1

回到impala-shell解释器,我们将原始的Impala托管表移开,并创建一个新的外部表,其中LOCATION子句指向我们设置所有分区子目录和数据文件的目录。

use external_partitions;
alter table logs rename to logs_original;
create external table logs (field1 string, field2 string, field3 string)
partitioned by (year string, month string, day string, host string)
row format delimited fields terminated by ','
location '/user/impala/data/logs';


由于分区子目录和数据文件在数据生命周期中来回移动,因此在Impala识别它们包含的数据文件之前,必须通过ALTER TABLE语句标识每个分区。

alter table logs add partition (year="2013",month="07",day="28",host="host1")
alter table log_type add partition (year="2013",month="07",day="28",host="host2");
alter table log_type add partition (year="2013",month="07",day="29",host="host1");
alter table log_type add partition (year="2013",month="08",day="01",host="host1");

我们为表发出REFRESH语句,在手动添加,删除或更改数据文件时始终是一种安全的做法。 然后准备好查询数据。 SELECT *语句说明来自我们普通CSV文件的数据在我们复制它的每个分区中被识别。 虽然在这种情况下只有几行,但我们在此测试查询中包含一个LIMIT子句,以防万一数据超出我们的预期。

refresh log_type;
select * from log_type limit 100;

在Impala和Hive之间切换回来

有时,您可能会发现切换到Hive shell以执行某些数据加载或转换操作很方便,尤其是在Impala当前可以查询但不能写入的文件格式(RCFile,SequenceFile和Avro)上。

无论何时通过Hive创建,删除或更改表或其他类型的对象,下次切换回impala-shell解释器时,都会发出一次性INVALIDATE METADATA语句,以便Impala识别新的或更改的对象。

无论何时通过Hive(或者甚至通过hdfs命令等手动HDFS操作)加载,插入或更改现有表中的数据,下次切换回impala-shell解释器时,都会发出一次性REFRESH table_name语句以便Impala识别新的或更改的数据。

有关此过程如何用于REFRESH语句的示例,请查看在Impala中创建RCFile和SequenceFile表,通过Hive加载数据,然后通过Impala查询数据的示例。有关这些示例,请参阅第734页的使用Impala表的RCFile文件格式和第737页的使用带有Impala表的SequenceFile文件格式。

有关此过程如何对INVALIDATE METADATA语句起作用的示例,请查看在Hive中创建和加载Avro表,然后通过Impala查询数据的示例。有关该示例,请参阅第729页的将Avro文件格式与Impala表一起使用。

注意:最初,Impala不支持UDF,但从Impala 1.2开始,Impala中提供了此功能。您最初通过Hive执行的一些INSERT … SELECT转换现在可以通过Impala完成。有关详细信息,请参阅第590页的用户定义函数(UDF)。

在Impala 1.2之前,需要在您连接并发出查询的每个Impala节点上发出REFRESH和INVALIDATE METADATA语句。在Impala 1.2及更高版本中,当您在任何Impala节点上发出这些语句中的任何一个时,结果将广播到集群中的所有Impala节点,使其在Hive中的每轮DDL或ETL操作之后真正成为一步操作。

使用CROSS JOIN运算符交叉连接和笛卡尔积

最初,Impala限制了连接查询,因此它们必须在连接运算符每一侧的表的列之间包含至少一个相等比较。由于通常由Impala处理的巨大表,任何产生完整笛卡尔积作为结果集的错误编码查询都会消耗大量的群集资源。

在Impala 1.2.2及更高版本中,当您在查询中使用CROSS JOIN运算符时,将取消此限制。您仍然无法从SELECT * FROM t1 JOIN t2等查询中删除所有WHERE子句,以生成两个表中的所有行组合。但是您可以使用CROSS JOIN运算符明确请求这样的笛卡尔积。通常,此操作适用于较小的表,其中结果集仍适合单个Impala节点的内存。

起初,我们使用等值连接查询,它只允许来自同一时间段和同一行星的角色相遇。

use experiments;
create table heroes (name string, era string, planet string);
create table villains (name string, era string, planet string);

insert into heroes values ('Tesla','20th century','Earth'),('Pythagoras','Antiquity','Earth'),('Zopzar','Far Future','Mars');

insert into villains values ('Caligula','Antiquity','Earth'),('John Dillinger','20th century','Earth'),('Xibulor','Far Future','Venus');

select concat(heroes.name,' vs. ',villains.name) as battle from heroes join villains where heroes.era = villains.era and heroes.planet = villains.planet;

+--------------------------+
| battle                   |
+--------------------------+
| Tesla vs. John Dillinger |
| Pythagoras vs. Caligula  |
+--------------------------+

在Impala 1.2.2之前,这种类型的查询是不可能的,因为所有连接都必须引用两个表之间的匹配值:

-- Cartesian product not possible in Impala 1.1.
select concat(heroes.name,' vs. ',villains.name) as battle from heroes join villains;


ERROR: NotImplementedException: Join between 'heroes' and 'villains'
 requires at least one conjunctive equality predicate between the two tables

使用Impala 1.2.2,我们稍微重写查询以使用CROSS JOIN而不是JOIN,现在结果集包括所有组合:

-- Cartesian product available in Impala 1.2.2 with the CROSS JOIN syntax.
select concat(heroes.name,' vs. ',villains.name) as battle from heroes cross join villains;


+-------------------------------+
| battle                        |
+-------------------------------+
| Tesla vs. Caligula            |
| Tesla vs. John Dillinger      |
| Tesla vs. Xibulor             |
| Pythagoras vs. Caligula       |
| Pythagoras vs. John Dillinger |
| Pythagoras vs. Xibulor        |
| Zopzar vs. Caligula           |
| Zopzar vs. John Dillinger     |
| Zopzar vs. Xibulor            |
+-------------------------------+

两个表中行的完整组合称为笛卡尔积。 这种类型的结果集通常用于创建网格数据结构。 您还可以通过包含未明确比较两个表之间的列的WHERE子句来过滤结果集。 以下示例显示如何生成用于图表的年份和季度组合的列表,然后显示仅包含选定季度的较短列表。

create table x_axis (x int);
create table y_axis (y int);
insert into x_axis values (1),(2),(3),(4);
insert into y_axis values (2010),(2011),(2012),(2013),(2014);

select y as year, x as quarter from x_axis cross join y_axis;
+------+---------+
| year | quarter |
+------+---------+
| 2010 | 1       |
| 2011 | 1       |
| 2012 | 1       |
| 2013 | 1       |
| 2014 | 1       |
| 2010 | 2       |
| 2011 | 2       |
| 2012 | 2       |
| 2013 | 2       |
| 2014 | 2       |
| 2010 | 3       |
| 2011 | 3       |
| 2012 | 3       |
| 2013 | 3       |
| 2014 | 3       |
| 2010 | 4       |
| 2011 | 4       |
| 2012 | 4       |
| 2013 | 4       |
| 2014 | 4       |
+------+---------+

select y as year, x as quarter from x_axis cross join y_axis where x in (1,3);

+------+---------+
| year | quarter |
+------+---------+
| 2010 | 1       |
| 2011 | 1       |
| 2012 | 1       |
| 2013 | 1       |
| 2014 | 1       |
| 2010 | 3       |
| 2011 | 3       |
| 2012 | 3       |
| 2013 | 3       |
| 2014 | 3       |
+------+---------+


处理具有未知架构的Parquet文件

随着数据管道开始包含更多方面(如NoSQL或松散指定的模式),您可能会遇到无法获知精确表定义的数据文件(特别是Parquet格式)的情况。本教程介绍如何围绕来自非Impala甚至非SQL源的数据构建Impala表,在这些源中,您无法控制表布局,并且可能不熟悉数据的特征。

本教程中使用的数据代表了从1987年10月到2008年4月的航空公司准点到达统计数据。请参阅2009 ASA Data Expo网站上的详细信息。您还可以看到列的说明;出于本练习的目的,请在检查模式之前等到教程之后,以更好地模拟现实情况,在这种情况下,您不能依赖关于数据值的范围和表示的假设和断言。

将数据文件下载到HDFS

首先,我们下载并解压缩数据文件。共有8个文件,总计1.4 GB。


$ wget -O airlines_parquet.tar.gz https://home.apache.org/~arodoni/
airlines_parquet.tar.gz
$ wget https://home.apache.org/~arodoni/airlines_parquet.tar.gz.sha512
$ shasum -a 512 -c airlines_parquet.tar.gz.sha512
airlines_parquet.tar.gz: OK
$ tar xvzf airlines_parquet.tar.gz
$ cd airlines_parquet/
$ du -kch *.parq
253M   4345e5eef217aa1b-c8f16177f35fd983_1150363067_data.0.parq
14M    4345e5eef217aa1b-c8f16177f35fd983_1150363067_data.1.parq
253M   4345e5eef217aa1b-c8f16177f35fd984_501176748_data.0.parq
64M    4345e5eef217aa1b-c8f16177f35fd984_501176748_data.1.parq
184M   4345e5eef217aa1b-c8f16177f35fd985_1199995767_data.0.parq
241M   4345e5eef217aa1b-c8f16177f35fd986_2086627597_data.0.parq
212M   4345e5eef217aa1b-c8f16177f35fd987_1048668565_data.0.parq
152M   4345e5eef217aa1b-c8f16177f35fd988_1432111844_data.0.parq
1.4G   total

接下来,我们将Parquet数据文件放在HDFS中,它们一起放在一个目录中,对目录和文件具有权限,以便impala用户能够读取它们。

打开包装后,我们看到最大的Parquet文件是253 MB。 将Parquet文件复制到HDFS以供Impala使用时,为了获得最大的查询性能,请确保每个文件都驻留在单个HDFS数据块中。 因此,我们选择大于任何单个文件的大小,并使用hdfs dfs -put命令中的参数Ddfs.block.size = 253m将其指定为块大小。

$ sudo -u hdfs hdfs dfs -mkdir -p /user/impala/staging/airlines
$ sudo -u hdfs hdfs dfs -Ddfs.block.size=253m -put *.parq /user/impala/
staging/airlines
$ sudo -u hdfs hdfs dfs -ls /user/impala/staging
Found 1 items
$ sudo -u hdfs hdfs dfs -ls /user/impala/staging/airlines
Found 8 items

创建数据库和表

将文件放在HDFS中的可访问位置,您可以创建一个使用这些文件中的数据的数据库表:

  • CREATE EXTERNAL语法和LOCATION属性指向相应HDFS目录中的Impala。

  • LIKE PARQUET’path_to_any_parquet_file’子句意味着我们跳过列名和类型列表; Impala自动从数据文件中获取列名和数据类型。 (目前,此技术仅适用于Parquet文件。)

  • 忽略有关缺少对HDFS中文件的READ_WRITE访问权限的警告; impala用户可以读取文件,这足以让我们试验查询并对其他表执行一些复制和转换操作。


$ impala-shell
> CREATE DATABASE airlines_data;
  USE airlines_data;
  CREATE EXTERNAL TABLE airlines_external
  LIKE PARQUET 'hdfs:staging/airlines/4345e5eef217aa1b-c8f16177f35fd983_1150363067_data.0.parq'
  STORED AS PARQUET LOCATION 'hdfs:staging/airlines';
WARNINGS: Impala does not have READ_WRITE access to path 'hdfs://
myhost.com:8020/user/impala/staging'

检查物理和逻辑架构

创建表后,我们会检查其物理和逻辑特征,以确认数据是否存在,以及我们可以使用的格式和形状。

  • SHOW TABLE STATS语句提供了表的高级摘要,显示了包含多少文件和总数据量。此外,它确认该表期望所有相关的数据文件都是Parquet格式。 (能够处理不同格式的各种HDFS数据文件意味着数据文件的格式与表期望数据文件所在的格式之间可能不匹配。)
  • SHOW FILES语句确认表中的数据具有原始Parquet文件的预期数量,名称和大小。
  • DESCRIBE语句(或其缩写DESC)确认Impala在从Parquet文件中读取元数据后自动创建的列的名称和类型。
  • DESCRIBE FORMATTED语句打印出一些额外的细节以及列定义。我们关心这项练习的部分是:
    • 表的包含数据库。
    • HDFS中关联数据文件的位置。
    • 该表是一个外部表,因此当我们完成实验时,Impala不会删除HDFS文件。
    • 该表设置为专门用于Parquet格式的文件。
SHOW TABLE STATS airlines_external;
SHOW FILES IN airlines_external;
DESCRIBE airlines_external;
DESCRIBE FORMATTED airlines_external;

分析数据

现在我们确信Impala表与底层Parquet文件之间的连接是可靠的,我们运行一些初始查询来了解数据的特征:总行数,范围以及确定的数量不同的值列。

> SELECT COUNT(*) FROM airlines_external;
+-----------+
| count(*)  |
+-----------+
| 123534969 |
+-----------+

NDV()函数返回许多不同的值,出于性能原因,这是在列中存在大量不同值时的估计值,但在基数小于16 K时是精确的。使用NDV()函数 这种探索而不是COUNT(DISTINCT colname),因为Impala可以在单个查询中评估多个NDV()函数,但只有COUNT DISTINCT的单个实例。


SElECT NDV(carrier), NDV(flight_num), NDV(tail_num),NDV(origin), NDV(dest) FROM airlines_external;

SELECT tail_num, COUNT(*) AS howmany FROM airlines_external GROUP BY tail_num;

SELECT DISTINCT dest FROM airlines_external WHERE dest NOT IN (SELECT origin FROM airlines_external);

+------+
| dest |
+------+
| CBM  |
| SKA  |
| LAR  |
| RCA  |
| LBF  |
+------+

SELECT DISTINCT dest FROM airlines_external WHERE dest NOT IN (SELECT DISTINCT origin FROM airlines_external);

+------+
| dest |
+------+
| CBM  |
| SKA  |
| LAR  |
| RCA  |
| LBF  |
+------+

SELECT DISTINCT origin FROM airlines_external WHERE origin NOT IN (SELECT DISTINCT dest FROM airlines_external);

Fetched 0 row(s) in 2.63

通过上述查询,我们发现有少量不同的航空公司,航班号以及始发地和目的地机场。从这个查询中跳出两件事:tail_num值的数量比我们预期的要小得多,并且目的地机场比原始机场多。让我们进一步挖掘。我们发现大多数tail_num值都是NULL。看起来这是一个没有准确填写的实验性色谱柱。我们要记住,如果我们使用这些数据作为起点,我们将忽略此列。我们还发现某些机场在ORIGIN列中有代表,但在DEST列中没有;现在我们知道我们不能依赖这些机场代码集相同的假设。

注意:第一个SELECT DISTINCT DEST查询大约需要40秒。我们希望对这种小于2 GB的小型数据集的所有查询最多只需几秒钟。原因是因为表达式NOT IN(SELECT origin FROM airlines_external)产生了1.23亿行的中间结果集,然后在每个数据节点上针对一小组目标机场运行1.23亿个比较。 NOT IN运算符在内部工作的方式意味着这个具有1.23亿行的中间结果集可能会通过网络传输到集群中的每个数据节点。在NOT IN子查询中应用另一个DISTINCT意味着中间结果集只有340个项目,从而减少了网络流量并减少了比较操作。添加了DISTINCT的查询效率提高了大约7倍。

接下来,我们尝试进行简单的计算,结果按年分解。这表明有些年份在通话时间栏中没有数据。这意味着我们可以在涉及特定日期范围的查询中使用该列,但我们不能指望它始终可靠。一个列是否包含任何NULL值的问题,如果是这样,它们的数量,比例和分布是什么,在进行数据集的初始探索时会反复出现。

但我们不能指望它永远是可靠的。 一个列是否包含任何NULL值的问题,如果是这样,它们的数量,比例和分布是什么,在进行数据集的初始探索时会反复出现。

SELECT year, SUM(airtime) FROM airlines_external GROUP BY year ORDER BY year DESC;

考虑到NULL值的概念,让我们回到我们发现有很多NULL的tail_num列。 让我们量化该列中的NULL和非NULL值以便更好地理解。 首先,我们只计算该行的总行数与该列中的非NULL值。 初始结果给出了相对较少的非NULL值的外观,但我们可以在单个查询中更清楚地分解它。 一旦我们得到COUNT(*)和COUNT(colname)数字,我们就可以在WITH子句中编码该初始查询,然后运行一个后续查询,对这些值执行多个算术运算。 看到所有行中只有百分之三的行具有tail_num列的非NULL值,这清楚地说明该列没有多大用处。

SELECT COUNT(*) AS 'rows', COUNT(tail_num) AS 'non-null tail numbers' FROM airlines_external;


+-----------+-----------------------+
| rows      | non-null tail numbers |
+-----------+-----------------------+
| 123534969 | 412968                |
+-----------+-----------------------+

WITH t1 AS
(SELECT COUNT(*) AS 'rows', COUNT(tail_num) AS 'nonnull' FROM airlines_external )
SELECT `rows`, `nonnull`, `rows` - `nonnull` AS 'nulls', (`nonnull` / `rows`) * 100 AS 'percentage non-null'
FROM t1;


+-----------+---------+-----------+---------------------+
| rows      | nonnull | nulls     | percentage non-null |
+-----------+---------+-----------+---------------------+
| 123534969 | 412968  | 123122001 | 0.3342923897119365  |
+-----------+---------+-----------+---------------------+

通过使用这些技术检查其他列,我们可以形成数据在整个表中的分布方式的心理图,以及哪些列对于查询目的最重要。 对于本教程,我们主要关注可能包含离散值的字段,而不是诸如actual_elapsed_time之类的列,其名称表示它们包含测量值。 一旦我们清楚地了解哪些问题值得提出,以及我们可能寻找什么样的趋势,我们就会深入研究这些专栏。 对于最初的初步探索,让我们看一下年份专栏。 一个简单的GROUP BY查询显示它具有明确定义的范围,可管理数量的不同值,以及不同年份的相对均匀的行分布。

SELECT MIN(year), MAX(year), NDV(year) FROM airlines_external;
+-----------+-----------+-----------+
| min(year) | max(year) | ndv(year) |
+-----------+-----------+-----------+
| 1987      | 2008      | 22        |
+-----------+-----------+-----------+

 SELECT year, COUNT(*) howmany FROM airlines_external
  GROUP BY year ORDER BY year DESC;
+------+---------+
| year | howmany |
+------+---------+
| 2008 | 7009728 |
| 2007 | 7453215 |
| 2006 | 7141922 |
| 2005 | 7140596 |
| 2004 | 7129270 |
| 2003 | 6488540 |
| 2002 | 5271359 |
| 2001 | 5967780 |
| 2000 | 5683047 |
| 1999 | 5527884 |
| 1998 | 5384721 |
| 1997 | 5411843 |
| 1996 | 5351983 |
| 1995 | 5327435 |
| 1994 | 5180048 |
| 1993 | 5070501 |
| 1992 | 5092157 |
| 1991 | 5076925 |
| 1990 | 5270893 |
| 1989 | 5041200 |
| 1988 | 5202096 |
| 1987 | 1311826 |
+------+---------+

我们可以使用这种初始原始格式的数据走得很远,就像我们从网上下载它一样。如果数据集被证明是有用的并且值得在Impala中持久化以进行大量查询,我们可能希望将其复制到内部表中,让Impala管理数据文件,并且可能需要重新组织以提高效率。在本教程的下一个阶段,我们将原始数据复制到分区表中,仍然是Parquet格式。基于年份列的分区允许我们使用诸如WHERE年= 2001或WHERE年BETWEEN 1989和1999之类的子句运行查询,这可以通过忽略所需范围之外的年份的所有数据来显着减少I / O. Impala可以仅使用特定年份分区中的数据文件,而不是读取所有数据然后决定匹配年份中的哪些行。为此,Impala会对数据文件进行物理重组,将每年的行放入每年值的单独HDFS目录中的数据文件中。在此过程中,我们还将摆脱已证明几乎完全为NULL的tail_num列。

第一步是创建一个新表,其布局与原始airlines_external表非常相似。我们将通过对第一个表进行逆向工程CREATE TABLE语句,然后稍微调整它以包含年份的PARTITION BY子句,并排除tail_num列来实现。 SHOW CREATE TABLE语句为我们提供了起点。

虽然我们可以将该输出编辑为新的SQL语句,但所有ASCII框字符都会使编辑不方便。 为了获得更精简的CREATE TABLE,我们使用-B选项重新启动impala-shell命令,这将关闭框绘制行为。

$ impala-shell -i localhost -B -d airlines_data;

SHOW CREATE TABLE airlines_external;
"CREATE EXTERNAL TABLE airlines_data.airlines_external (
  year INT COMMENT 'inferred from: optional int32 year',
  month INT COMMENT 'inferred from: optional int32 month',
  day INT COMMENT 'inferred from: optional int32 day',
  ..........
  late_aircraft_delay')
STORED AS PARQUET
LOCATION 'hdfs://a1730.example.com:8020/user/impala/staging/airlines'
TBLPROPERTIES ('numFiles'='0', 'COLUMN_STATS_ACCURATE'='false',
  'transient_lastDdlTime'='1439425228', 'numRows'='-1', 'totalSize'='0',
  'rawDataSize'='-1')"

将CREATE TABLE语句复制并粘贴到文本编辑器中进行微调后,我们退出并重新启动impala-shell而不使用-B选项,以切换回常规输出。

接下来,我们运行我们从SHOW CREATE TABLE输出中调整的CREATE TABLE语句。 我们保留了STORED AS PARQUET子句,因为我们想要稍微重新排列数据,但仍然保持高性能的Parquet格式。 LOCATION和TBLPROPERTIES条款与此新表无关,因此我们将其编辑出来。 因为我们要根据year列对新表进行分区,所以我们将该列名(及其类型)移动到新的PARTITIONED BY子句中。

CREATE TABLE airlines_data.airlines
 (month INT,
  day INT,
  dayofweek INT,
  dep_time INT,
  crs_dep_time INT,
  arr_time INT,
  crs_arr_time INT,
  carrier STRING,
  flight_num INT,
  actual_elapsed_time INT,
  crs_elapsed_time INT,
  airtime INT,
  arrdelay INT,
  depdelay INT,
  origin STRING,
  dest STRING,
  distance INT,
  taxi_in INT,
  taxi_out INT,
  cancelled INT,
  cancellation_code STRING,
  diverted INT,
  carrier_delay INT,
  weather_delay INT,
  nas_delay INT,
  security_delay INT,
  late_aircraft_delay INT)
PARTITIONED BY (year INT)
STORED AS PARQUET
;

接下来,我们使用INSERT语句将原始表中的所有行复制到这个新行中。 (我们编辑了CREATE TABLE语句以生成一个INSERT语句,其列名以相同的顺序排列。)唯一的变化是添加PARTITION(year)子句,并将year列移动到SELECT列表的最后一行。 INSERT语句。 指定PARTITION(年份)而不是诸如PARTITION(year = 2000)之类的固定值意味着Impala根据SELECT列表中最后一列的值计算出每行的分区值。 这是第一个合法占用任何实际时间的SQL语句,因为来自不同年份的行在集群周围进行了混乱; 在写入一个或多个新数据文件之前,进入每个分区的行将在一个节点上收集。

INSERT INTO airlines_data.airlines
  PARTITION (year)
  SELECT
    month,
    day,
    dayofweek,
    dep_time,
    crs_dep_time,
    arr_time,
    crs_arr_time,
    carrier,
    flight_num,
    actual_elapsed_time,
    crs_elapsed_time,
    airtime,
    arrdelay,
    depdelay,
    origin,
    dest,
    distance,
    taxi_in,
    taxi_out,
    cancelled,
    cancellation_code,
    diverted,
    carrier_delay,
    weather_delay,
  nas_delay,
  security_delay,
  late_aircraft_delay,
  year
FROM airlines_data.airlines_external;

一旦分区或联接查询发挥作用,拥有Impala可用于优化相应表上查询的统计信息非常重要。 COMPUTE INCREMENTAL STATS语句是收集分区表的统计信息的方法。 然后,SHOW TABLE STATS语句确认每个分区的统计信息都已就绪,并且还说明了每个分区中有多少文件和原始数据。

> COMPUTE INCREMENTAL STATS airlines;
+-------------------------------------------+
| summary                                   |
+-------------------------------------------+
| Updated 22 partition(s) and 27 column(s). |
+-------------------------------------------+
> SHOW TABLE STATS airlines;



在这一点上,我们理智地检查我们做的分区。所有分区都只有一个文件,这个文件偏低。包含子句WHERE year = 2004的查询将只读取单个数据块;该数据块将由单个数据节点读取和处理;因此,对于针对一年的查询,群集中的所有其他节点将处于空闲状态,而所有工作都在一台计算机上进行。甚至可能偶然(取决于HDFS复制因子和数据块在群集中的分布方式),过滤器选择的多年分区,例如WHERE年BETWEEN 1999和2001,都可以由相同的数据读取和处理节点。每个分区拥有的数据文件越多,您可以获得的并行性越多,在特定节点上出现“热点”的可能性就越小,因此通过拥有一个大型集群可以提高性能。

但是,数据文件越多,每个数据的数据就越少。如果每个节点只读取几兆字节,那么在并行查询中划分工作的开销可能不值得。对于Parquet数据块,50或100兆字节是一个合适的大小; 9或37兆字节偏小。也就是说,基于此分区方案我们最终得到的数据分布处于合理(合理大文件)和次优(每个分区中的少量文件)之间的边界线上。在实践中查看它的工作情况的方法是对原始平面表和新分区表运行相同的查询,并比较时间。

Spoiler:在这种情况下,对于我的特定4节点集群及其特定的数据块分布和我的特定探索性查询,针对分区表的查询始终比针对未分区表的相同查询运行得更快。但是如果没有一些真正的测量结果,我无法确定是否会出现这种情况。以下是我用来绘制结论的一些查询,首先针对airlines_external(无分区),然后针对AIRLINES(按年份分区)。 AIRLINES查询始终更快。更改数据量,更改群集大小,运行已执行或未引用分区键列的查询或其他因素可能会更改结果以支持一个表布局或另一个。

注意:如果发现每个分区的卷只有几十兆字节,请考虑降低分区的粒度。例如,不是按年,月和日进行分区,而是按年和月分区,甚至按年分区。在并行查询中有效分配工作的理想布局是每个Parquet文件几十甚至几百兆字节,并且每个分区中的Parquet文件数量略高于数据节点的数量。

> SELECT SUM(airtime) FROM airlines_external;
+--------------+
| 8662859484   |
+--------------+
> SELECT SUM(airtime) FROM airlines;
+--------------+
| 8662859484   |
+--------------+
> SELECT SUM(airtime) FROM airlines_external WHERE year = 2005;
+--------------+
| 708204026    |
+--------------+
> SELECT SUM(airtime) FROM airlines WHERE year = 2005;
+--------------+
| 708204026    |
+--------------+

现在我们终于可以从原始数据文件中分析这个数据集了,我们不知道它们包含哪些列。 让我们看一下航班的播出时间是否会因星期几而有所不同。 我们可以看到第6天的平均值略高一点; 也许周六是一个忙碌的飞行日,飞机必须在降落前在目的地机场停留更长时间。

SELECT dayofweek, AVG(airtime) FROM airlines GROUP BY dayofweek ORDER BY dayofweek;

为了看看这个明显的趋势是否会随着时间的推移而持续,让我们按周计算同样的细分,但也会按年分配。 现在我们可以看到,第6天每年的平均播出时间一直较长。 我们还可以看到平均播出时间随着时间的推移而增加。 在1987年至1994年期间,该列的NULL存在表明涉及此列的查询需要限制在1995年及更高的日期范围内。

SELECT year, dayofweek, AVG(airtime) FROM airlines GROUP BY year, dayofweek ORDER BY year DESC, dayofweek;

你可能感兴趣的:(Impala)