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中可读。


《Spark原理深入与编程实战》