Hive读取Spark生成的Parquet数据时出现org.apache.parquet.io.ParquetDecodingException问题的原因及解决办法
2021-11-22 12:13:43.0
问题描述
我们在Spark中,使用DataFrame.saveAsTable()方法将DataFrame存入到Hive数据仓库中。然后在hive shell中直接使用Hive QL查询表数据,出现如下异常:
Failed with exception java.io.IOException:org.apache.parquet.io.ParquetDecodingException: Can not read value at 0 in block -1 in file hdfs://xueai8:8020/user/hive/warehouse/...
问题产生的原因
当使用Spark写入Parquet数据,使用Hive读取时,会出现上述问题。
这个问题是由于在Spark和Hive中使用不同的parquet约定造成的。在Hive中,十进制数据类型被表示为固定字节(INT 32)。而在Spark 1.4或更高版本中,默认惯例为十进制数据类型使用Standard Parquet表示。根据基于列数据类型精度的Standard Parquet表示,底层表示发生了变化。
例如:
DECIMAL可以用于注释以下类型:
- int32: 用于 1 <= precision <= 9
- int64: 用于 1 <= precision <= 18; precision < 10 会产生一个警告
关于DECIMAL,请参考这里
因此,这个问题只发生在在不同的Parquet约定中具有不同表示形式的数据类型的使用上。
即:
如果数据类型是DECIMAL(10,3),两种约定都将其表示为INT32,因此我们不会遇到问题。如果我们不知道数据类型的内部表示,那么读时使用与写入相同的约定是安全的。在Hive中,我们没有选择Parquet约定的灵活性。但在Spark中,我们可以选择Parquet约定。
解决方法
Spark写入Parquet数据的约定是可配置的。这是由属性spark.sql.parquet.writeLegacyFormat决定的。
默认值为false。如果设置为true,则Spark写入Parquet数据的约定与Hive相同。这将有助于解决问题。
--conf "spark.sql.parquet.writeLegacyFormat=true"
如果为true,数据将以Spark 1.4及更早版本的方式写入。例如,十进制值将被写入Apache Parquet的固定长度字节数组格式,其他系统如Apache Hive和Apache Impala使用这种格式。如果为false,则使用Parquet中较新的格式。例如,小数将以基于整数的格式写入。如果Parquet输出用于不支持这种新格式的系统,则设置为true。
如果已经使用Spark生成了数据,那么在设置上述属性后,必须重新生成相同的数据,使其在Hive中可读。