spark 批量读取HDFS(hive分区)parquet文件

情况说明:数据以parquet文件形式保存在HDFS上,数据中的某一列包含了日期(例如:2017-12-12)属性,根据日期对数据分区存储,如下图所示:
这里写图片描述
项目需求:
在项目中想要读取某一个月的数据,肿么办?
解决方法:
spark中读取本地文件的方法如下:

sparkSession.read.parquet("hdfs://path")

方法一:
要读取多个文件,文件的路径中有一段公共路径。这样,首先想到的方法就是通过通配符来读取多个文件,对应的代码如下:

sparkSession.read.parquet("hdfs://hzgc:9000/user/hive/warehouse/person_table/date=2017-12-*")

理想是美好的,但是现实是残酷的,代码运行结果报错:

<think>我们已有关于Spark读写Hive表的通用方法,现在用户明确要求读写Hive分区表的方法。 根据引用[3]、[4]、[5]以及之前的知识,读写Hive分区表的关键点包括: 1. 创建分区表(定义分区字段和存储格式) 2. 写入时指定分区字段(动态分区或静态分区) 3. 读取时利用分区剪枝提高性能 参考引用[4]中的代码片段,它展示了如何创建一个分区表并插入数据(使用静态分区): - 创建表时使用`partitioned by`指定分区字段 - 插入数据时在`insert`语句中指定分区值(静态分区) 引用[3]提到了在查询分区表时如何正确处理NULL值。 引用[5]提到使用`saveAsTable`时配合`partitionBy`可能会遇到问题(不生成ORC文件),因此需要注意存储格式和配置。 下面将详细说明读写Hive分区表的方法。 ### Spark读写Hive分区表的方法 #### 1. 创建分区表 在Spark中创建Hive分区表,可以使用Spark SQL的`CREATE TABLE`语句,并指定分区字段和存储格式。 示例(参考引用[4]): ```scala val createTableSql = """ |CREATE TABLE IF NOT EXISTS person ( | id STRING, | age INT |) |PARTITIONED BY (birthDay INT) |STORED AS PARQUET """.stripMargin spark.sql(createTableSql) ``` 关键点: - `PARTITIONED BY`指定分区字段(注意:分区字段不能出现在表字段中,因为它们是同一列,但分区字段会作为目录存储) - 存储格式(如`STORED AS PARQUET`)可根据需要选择(ORC、TEXTFILE等) #### 2. 写入分区表 写入分区表有两种方式:静态分区和动态分区。 **方式一:静态分区(指定分区值)** 在插入数据时,明确指定分区字段的值。适用于已知分区值的情况。 示例(参考引用[4]): ```scala val insertSql = s""" |INSERT OVERWRITE TABLE person PARTITION (birthDay=$birthDay) |SELECT id, age |FROM person_record """.stripMargin spark.sql(insertSql) ``` **方式二:动态分区(自动根据数据设置分区)** 动态分区会根据数据中的分区字段自动创建分区。需要先设置动态分区相关的配置。 示例代码: ```scala // 启用动态分区并设置相关配置 spark.conf.set("hive.exec.dynamic.partition", "true") spark.conf.set("hive.exec.dynamic.partition.mode", "nonstrict") // 假设df包含分区字段birthDay,且已注册为临时表 // 或者直接使用DataFrame API写入 df.write .partitionBy("birthDay") // 指定分区字段 .mode(SaveMode.Append) // 追加模式 .format("hive") // 指定格式为hive .saveAsTable("person") // 表名 // 或者使用SQL方式(推荐) df.createOrReplaceTempView("temp_person") spark.sql(""" INSERT OVERWRITE TABLE person PARTITION (birthDay) SELECT id, age, birthDay FROM temp_person """) ``` 注意: - 动态分区的字段必须在SELECT子句的最后,且与分区字段的顺序一致(如果是多个分区字段,则按顺序排列)。 - 使用动态分区前,需要设置`hive.exec.dynamic.partition`和`hive.exec.dynamic.partition.mode`(通常设为`nonstrict`,否则必须指定至少一个静态分区)。 **写入模式**: - `SaveMode.Overwrite`:覆盖整个表或指定分区(注意:在动态分区时,会覆盖所有匹配的分区,而静态分区则只覆盖指定分区) - `SaveMode.Append`:追加数据 引用[5]中提到,使用`saveAsTable`并配合`partitionBy`时,可能不会在HDFS中生成ORC文件。因此,建议在保存后检查HDFS目录,确保数据写入正确。或者使用SQL语句插入更可靠。 #### 3. 读取分区读取分区表与读取普通表相同,但可以利用分区剪枝提高性能。 示例: ```scala // 直接读取整个表 val df = spark.sql("SELECT * FROM person") // 读取特定分区(利用分区剪枝) val dfWithPartition = spark.sql("SELECT * FROM person WHERE birthDay=20190919") ``` 分区剪枝(Partition Pruning)是Spark优化查询的一种方式,当WHERE子句包含分区字段的条件时,Spark只会读取相应分区的数据,从而提高效率。 引用[3]中提到,当分区字段包含NULL值时,查询条件`WHERE city is NULL`在Spark3中可以正确执行(无论`spark.sql.hive.convertMetastoreOrc`配置如何),但在Spark2中需要依赖该配置。因此,在处理NULL分区值时需注意Spark版本。 #### 4. 注意事项 - **分区字段类型**:分区字段在表中是虚拟列,其实际值由目录名称决定(字符串)。因此,在查询时,分区字段的条件应与目录名称的类型匹配(通常为字符串)。但若在创建表时指定了分区字段的类型(如INT),则可以使用对应类型。 - **分区表的数据存储**:每个分区HDFS中是一个单独的目录(如`/user/hive/warehouse/person/birthDay=20190919`)。 - **动态分区的限制**:动态分区可能会创建大量分区,因此需要合理配置,避免超出Hive的默认限制(如每个节点创建的最大动态分区数`hive.exec.max.dynamic.partitions.pernode`)。 - **存储格式**:创建表时指定的存储格式应与写入数据的格式一致,否则可能导致读取失败。 ### 代码示例:完整读写Hive分区表 以下示例展示从旧表读取数据,处理后写入分区表(使用动态分区): ```scala import org.apache.spark.sql.{SaveMode, SparkSession} object HivePartitionTableExample { def main(args: Array[String]): Unit = { val spark = SparkSession.builder() .appName("HivePartitionTableExample") .master("local[*]") .enableHiveSupport() // 启用Hive支持 .getOrCreate() // 设置动态分区属性 spark.conf.set("hive.exec.dynamic.partition", "true") spark.conf.set("hive.exec.dynamic.partition.mode", "nonstrict") try { // 读取源表(假设源表有id, age, birthDay字段) val sourceDF = spark.sql("SELECT id, age, birthDay FROM person_record") // 创建分区表(如果不存在) spark.sql(""" CREATE TABLE IF NOT EXISTS person ( id STRING, age INT ) PARTITIONED BY (birthDay INT) STORED AS PARQUET """) // 写入分区表(动态分区) sourceDF.write .partitionBy("birthDay") .mode(SaveMode.Overwrite) .saveAsTable("person") // 验证:读取特定分区 spark.sql("SELECT * FROM person WHERE birthDay=20190919").show() } finally { spark.stop() } } } ``` ### 总结 - **创建分区表**:使用`CREATE TABLE ... PARTITIONED BY ...`语句。 - **写入分区**:可选择静态分区(明确指定值)或动态分区(自动推断),注意动态分区的配置。 - **读取分区**:使用普通查询,但WHERE子句包含分区字段可触发分区剪枝优化。 - **版本兼容性**:Spark3对Hive分区表的支持更好(尤其是处理NULL值)[^3]。 如果遇到写入问题(如未生成文件),建议检查HDFS目录权限和存储格式配置,或改用SQL插入语句。 §§相关问题§§ 1. 如何设置Spark动态分区参数以避免创建过多分区? 2. Spark读取Hive分区表时如何利用分区剪枝提高性能? 3. 如何处理Hive分区表中的NULL值分区? </think>### Spark读写Hive分区表的方法 在Spark中高效操作Hive分区表需要注意分区创建、读写优化和配置管理。以下是关键步骤和最佳实践,结合引用内容整理: --- #### 1. **创建分区表** 使用`PARTITIONED BY`定义分区字段,并指定存储格式(推荐Parquet/ORC): ```scala import org.apache.spark.sql.SparkSession val spark = SparkSession.builder() .appName("HivePartitionTable") .enableHiveSupport() // 启用Hive集成[^1] .getOrCreate() // 创建分区表(birthDay为分区字段) spark.sql(""" CREATE TABLE IF NOT EXISTS person ( id STRING, age INT ) PARTITIONED BY (birthDay INT) STORED AS PARQUET // 指定列式存储格式[^2] """) ``` **关键点**: - 分区字段需独立于数据字段(如`birthDay`未在表结构中重复定义)[^4] - `STORED AS PARQUET/ORC`优化存储和查询性能[^2] --- #### 2. **写入分区表** ##### (1) 静态分区写入(明确指定分区值) ```scala val insertSql = s""" INSERT OVERWRITE TABLE person PARTITION(birthDay=20190919) SELECT id, age FROM person_record """ spark.sql(insertSql) // 直接执行SQL插入[^4] ``` ##### (2) 动态分区写入(自动匹配分区字段) ```scala // 启用动态分区配置 spark.conf.set("hive.exec.dynamic.partition", "true") spark.conf.set("hive.exec.dynamic.partition.mode", "nonstrict") // DataFrame API写入 sourceDF.write .partitionBy("birthDay") // 指定分区字段[^5] .mode("overwrite") // 覆盖模式 .saveAsTable("person") // 自动创建分区 ``` **注意事项**: - 动态分区需设置`hive.exec.dynamic.partition.mode=nonstrict`[^5] - 分区字段必须在DataFrame最后一列(与表定义顺序一致) --- #### 3. **读取分区表** 利用分区剪枝(Partition Pruning)提升查询性能: ```scala // 直接读取全表 val fullDF = spark.table("person") // 分区剪枝查询(只扫描特定分区) val filteredDF = spark.sql("SELECT * FROM person WHERE birthDay=20190919") // 处理NULL分区Spark 3+支持) spark.sql("SELECT * FROM partitioned_table WHERE city IS NULL") // 有效查询[^3] ``` **优化建议**: - 查询时用`WHERE`过滤分区字段,减少I/O - Spark 3+默认支持`NULL`分区查询,Spark 2需设置`spark.sql.hive.convertMetastoreOrc=true`[^3] --- #### 4. **注意事项与故障处理** | **问题** | **解决方案** | |-------------------------|----------------------------------------------------------------------------| | 动态分区未生成文件 | 检查HDFS权限,确保`saveAsTable`前配置存储格式(如`.format("parquet")`)[^5] | | 分区字段顺序错误 | 确保DataFrame列顺序:数据字段在前,分区字段在后 | | 分区数超限 | 设置参数:`spark.conf.set("hive.exec.max.dynamic.partitions", 10000)` | | 读取性能低 | 使用列式存储(Parquet/ORC),并启用谓词下推[^2] | --- #### 5. **完整示例** ```scala // 读取源表 val sourceDF = spark.table("person_record") // 写入动态分区表 sourceDF.write .partitionBy("birthDay") .mode("append") // 避免覆盖 .saveAsTable("person") // 验证分区写入 spark.sql("SHOW PARTITIONS person").show() ``` --- ### 总结 - **创建分区表**:明确分区字段和存储格式(Parquet/ORC) - **写入优化**:静态分区简单直接,动态分区需配置参数 - **读取加速**:通过`WHERE`条件实现分区剪枝 - **NULL处理**:Spark 3+直接支持`IS NULL`查询[^3] - **数据保护**:写入模式优先用`append`避免误覆盖
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值