我将尝试一次性解释 Hive 中的分区是什么。首先是
何时使用表分区
-
表分区适用于:
- 读取整个数据集耗时过长
- 查询几乎总是按分区列过滤
- 分区列有合理数量的不同值
ETL 过程的数据生成按文件名或目录名拆分数据
- 分区列值不在数据本身中
- 不要对具有许多唯一值的列进行分区
- 示例:按名字对客户进行分区
创建分区表
要创建分区表,请在 CREATE TABLE 语句中使用 PARTITIONED BY 子句。
必须指定分区列的名称和类型
在 PARTITIONED BY 子句中,并且仅在 PARTITIONED BY 子句中。
它们不得同时出现在所有其他列的列表中。
CREATE TABLE customers_by_country
(cust_id STRING, name STRING)
PARTITIONED BY (country STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
上面显示的 CREATE TABLE 语句示例创建了表 customers_by_country,
它由名为 country 的 STRING 列分区。
请注意,国家列仅出现在 PARTITIONED BY 子句中,
而不是在它上面的列列表中。
此示例仅指定一个分区列,但您可以通过使用指定多个
PARTITIONED BY 子句中以逗号分隔的列列表。
除了这些特定的差异之外,这个 CREATE TABLE 语句是相同的
作为用于创建等效非分区表的语句。
表分区的实现方式大多是透明的
向使用 Hive 发出查询的用户。
分区列就是所谓的虚拟列,因为它的值不存储在数据文件中。
以下是customers_by_country 上DESCRIBE 命令的结果;
它显示分区列国家,就好像它是表中的普通列一样。
您可以在 SELECT 语句的任何常用子句中引用分区列。
name type comment
cust_id string
name string
country string
您可以动态或静态地在分区表中加载数据
使用动态分区加载数据
将数据加载到分区表中的一种方法是使用动态分区,
它会在您加载数据时使用分区列中的值自动定义分区。
(另一种方法是使用静态分区手动定义分区)
要使用动态分区,您必须使用 INSERT 语句加载数据。
在 INSERT 语句中,您必须使用 PARTITION 子句列出分区列。
您插入的数据必须包含分区列的值。
分区列必须是您要插入的数据中最右边的列,
并且它们的顺序必须与它们在 PARTITION 子句中出现的顺序相同。
INSERT OVERWRITE TABLE customers_by_country
PARTITION(country)
SELECT cust_id, name, country FROM customers;
上面显示的示例使用了 INSERT ... SELECT 语句
使用动态分区将数据加载到 customers_by_country 表中。
请注意,分区列 country 已包含在内
在 PARTITION 子句中,并在 SELECT 列表中最后指定。
Hive 执行此语句时,会自动创建分区
为国家列,并根据国家列中的值将数据加载到这些分区中。
分区子目录中生成的数据文件不包含国家列的值。
由于根据数据文件所在的子目录知道国家/地区,
在数据文件中包含国家值也是多余的。
查看customers_by_country 目录的内容。
它现在应该为国家列中的每个值都有一个子目录。
- 查看其中一个目录中的文件。
请注意,该文件包含来自该国家/地区的客户的行,
没有其他人;另请注意,不包括国家/地区值。
注意: Hive 包含一项安全功能,可防止用户
避免意外创建或覆盖大量分区。
(有关此内容的更多信息,请参阅“使用分区的风险”。)
默认情况下,Hive 将属性 hive.exec.dynamic.partition.mode 设置为严格。
这会阻止您使用动态分区,但您仍然可以使用静态分区。
您可以通过设置在 Hive 中禁用此安全功能
属性hive.exec.dynamic.partition.mode 为非严格:
SET hive.exec.dynamic.partition.mode=nonstrict;
然后您可以使用 INSERT 语句动态加载数据。
在 Beeline 中设置的 Hive 属性仅适用于当前会话,
因此,下次您启动 Hive 会话时,此属性将设置回严格。
但如有必要,您或您的系统管理员可以永久配置属性。
当您在分区表上运行一些 SELECT 查询时,如果表足够大,您会注意到运行时间的显着差异。
请注意,您查询该表的方式与查询客户表的方式没有任何不同。
使用静态分区加载数据
将数据加载到分区表中的一种方法是使用静态分区,
您可以在其中手动定义不同的分区。
使用静态分区,您可以使用 ALTER TABLE ... ADD PARTITION 语句手动创建分区,
然后将数据加载到分区中。
例如,这个 ALTER TABLE 语句为巴基斯坦 (pk) 创建分区:
ALTER TABLE customers_by_country
ADD PARTITION (country='pk');
注意分区列名(即国家/地区)以及定义此分区的具体值,
即 pk,都在 ADD PARTITION 子句中指定。
这会在 customers_by_country 表目录中创建一个名为 country=pk 的分区目录。
巴基斯坦分区创建后,您可以使用 INSERT ... SELECT 语句将数据添加到分区中:
INSERT OVERWRITE TABLE customers_by_country
PARTITION(country='pk')
SELECT cust_id, name FROM customers WHERE country='pk'
注意在 PARTITION 子句中,分区列名是国家,
和指定的具体值,即 pk,都指定,就像在用于创建分区的 ADD PARTITION 命令中一样。
另请注意,在 SELECT 语句中,分区列不包含在 SELECT 列表中。
最后,请注意 SELECT 语句中的 WHERE 子句仅选择来自巴基斯坦的客户。
使用静态分区,您需要对每个分区重复这两个步骤:
首先创建分区,然后添加数据。
您实际上可以使用任何方法来加载数据;您不需要使用 INSERT 语句。
您可以改为使用 hdfs dfs 命令或 LOAD DATA INPATH 命令。
但是,无论您如何加载数据,您都有责任确保数据存储在正确的分区子目录中。
例如,巴基斯坦客户的数据必须存储在巴基斯坦分区子目录中,
其他国家/地区客户的数据必须存储在这些国家/地区的分区子目录中。
静态分区在加载数据时最有用
进入表已经根据分区列分为文件,
或者当数据以与分区列一致的方式增长时:
例如,假设您的公司在不同的国家开设了一家新店,
像新西兰 ('nz'),你会得到一个新客户的数据文件,这些客户都来自那个国家。
您可以轻松添加新分区并将该文件加载到其中。
使用分区的风险
使用分区时的一个主要风险是创建的分区会导致您遇到小文件问题。
发生这种情况时,对表进行分区实际上会降低查询性能
(与使用分区时的目标相反)因为它会导致创建太多小文件。
这在使用动态分区时更有可能发生,但它仍然可以
静态分区会发生 - 例如,如果您将新分区添加到销售表
每天包含前一天的销售额,
而且每天的数据都不是特别大。
在选择分区时,您希望在太多分区之间取得平衡
(导致小文件问题)和太少的分区(提供性能的好处很少)。
分区列或列应具有合理数量的值
对于分区 - 但是您应该认为合理的内容很难量化。
使用动态分区特别危险,因为如果你不小心,
在具有太多不同值的列上进行分区很容易。
想象一个用例,您经常在其中查找属于
您将在查询中指定的时间范围。
您可能认为在与时间相关的列上进行分区是个好主意。
但是 TIMESTAMP 列的时间可以达到纳秒,所以每一行都可以有一个唯一的值;
对于分区列来说,这将是一个糟糕的选择!甚至到分钟或小时都可以创造
分区太多,具体取决于数据的性质;
按较大的时间单位(如日、月甚至年)进行分区可能是更好的选择。
作为另一个例子,考虑一个雇员表。
这有五列:empl_id、first_name、last_name、salary 和 office_id。
在继续阅读之前,请考虑一下,其中哪些可能是合理的分区
- empl_id 列是唯一标识符。
如果那是您的分区列,那么您将为每个员工都有一个单独的分区,
每个人都会有一行。
此外,您不太可能会进行大量查询以查找特定值,
甚至是特定范围的值。这是一个糟糕的选择。
- first_name 列不会有每个员工一个,但可能会有很多列只有一行。
- last_name 也是如此。
此外,与 empl_id 一样,您不太可能需要基于这些列的过滤查询。这些也是糟糕的选择。
- 工资栏也会有很多划分
(如果你的薪水像我们的样本表那样按美分计算,而不是按美元计算,那就更是如此)。
虽然您有时可能想查询工资范围,
您不太可能想要使用个人工资。
所以薪水是一个糟糕的选择。
- 更有限的salary_grades 规范,如salary_grades 表中的规范,
如果您的用例涉及经常按工资等级查看数据,这可能是合理的。
- office_id 列标识员工工作的办公室。
即使您有一家在许多城市设有办事处的大公司,它的独特价值也会少得多。
可以想象,您的用例可能是频繁过滤
您的员工数据也基于办公地点。所以这将是一个不错的选择。
您还可以使用多个列并创建嵌套分区。
例如,客户数据集可能包括 country 和 state_or_province 列。
您可以按国家/地区划分,然后按 state_or_province 进一步划分,因此来自安大略省的客户,
加拿大将位于 country=ca/state_or_province=on/ 分区目录中。
这对于您希望按国家或按州或省访问的大量数据非常有帮助。
但是,使用多列会增加创建过多分区的风险,因此在这样做时必须格外小心。
创建过多分区的风险是 Hive 包含该属性的原因
hive.exec.dynamic.partition.mode,默认设置为严格,必须重置为非严格才能创建分区。
当您要动态加载数据时,而不是自动和机械地重置该属性,
以此为契机考虑分区列
并且可能检查加载数据时获得的唯一值的数量。
仅此而已。