sqoop – import导入parquet的坑

sqoop import导入mysql数据时使用parquet是为了列存压缩节约磁盘空间,但parquet本身是类似于protocolbuf的强schema格式,每一列的数据类型都是明确定义的,这就带来了一些坑点,下面列举一下。

坑1:JDBC类型问题

sqoop导入mysql时是使用JDBC连接Mysql,然后将数据读取回内存里保存到Java数据类型里,这里面就存在一个类型问题。

tinyint问题

首先是发现mysql的tinyint字段,经过JDBC读取后变成了boolean类型(发现该问题的过程后续说),定位是因为JDBC有一个选项叫做:

tinyInt1isBit=true

默认为true,它会将只有0和1值的tinyint字段存储到java的boolean类型中,最终导致写入parquet时的schema也变成了boolean类型,而我建hive表时还傻傻的用tinyint,导致hive读取parquet文件报错。

datetime类型问题

接下来发现mysql的datetime字段,原本应该是”2020-11-12 10:12:00″这样的字符串时间,结果jdbc读回内存时就存储到Long类型变量里了,然后导致写入parquet时将schema指定为int64,而hive建表时我采用的string类型,所以导致查询报错。

该问题没有JDBC参数可以影响行为,只能在sqoop import时对datetime字段进行一次显式的date_format操作,类似于这样:

sqoop import “select DATE_FORMAT(create_time, ‘%Y-%m-%d %H:%i:%s’) create_time”

这样就可以令JDBC将其读取到String变量类型,然后parquet的schema就是utf8 binary类型,最后hive表可以令字段为string类型即可。

datetime空值问题

还是mysql的datetime,发现表里有0000-00-00 00:00:00这样的值,当sqoop import时JDBC报错说无法转换该字段。

这个我们可以直接加一个JDBC参数:

zeroDateTimeBehavior=convertToNull

它的意思是JDBC会将全0的datetime读取为Null,这样就可以避免JDBC无法处理的错误了。

但是我们其实并不需要这个参数,只需要继续使用上面的date_format来处理一下该字段,直接将其变成字符串类型,那么JDBC就不会去尝试解析这个时间字符串了,自然也就不会报错了。

unsigned问题

然后发现某些mysql列配置了unsigned无符号类型,这时候原本mysql int列被JDBC读取回来时就会被保存到Long类型变量里,因为无符号int是无法保存到Java的有符号Integer里的,类似情况不止这一种。

因为Java已经存储为Long了,所以导致写parquet文件时schema也变成了int64,然而此时我建hive表如果还用int类型字段的话,当我们select查询hive的时候就会出现hive试图将parquet的int64转成Integer的向下转型情况,自然就报错了。

解决办法就是注意这种case,如果不清楚sqoop导出的parquet schema到底是什么类型,我们最好使用parquet官方工具解析一下文件看一下schema,并根据schema建hive表,确保一次性正确:

parquet-tools官网:https://github.com/apache/parquet-mr/tree/master/parquet-tools

编译jar包,然后执行命令查看某个parquet文件的schema:

hadoop jar /home/hadoop/workspace/service/parquet-tools.jar schema -d /warehouse/safe/ods/ods_basedata_smzdm_product_category/dt=2020-11-25/3a263b81-caea-405f-b023-22196b34d0f1.parquet

会打印类似这样的东西:

message AutoGeneratedSchema {
optional int64 ID;
optional binary title (UTF8);
optional binary nicktitle (UTF8);
optional int64 author_id;
optional binary create_time (UTF8);
optional int64 last_editor_id;
optional binary last_edit_time (UTF8);
optional int32 is_deleted;
optional int32 rank;
optional int32 parent_id;
optional binary parent_ids (UTF8);
optional int32 smzdm_category_id;
optional int32 category_sort;
optional binary url_nicktitle (UTF8);

如果是binary utf8则Hive采用string,如果int32就用hive int,int64就用hive bigint,其他情况类推。

坑2:sqoop导入空目录

发现sqoop import后,因为当日没有增量数据,所以sqoop仅仅创建了一个当天日期的空目录,里面没有parquet文件。

当后续执行hive的load data inpath加载sqoop导出数据时,就会因为空目录下没有文件的原因而导致load失败。

hive的load data命令会忽略sqoop目录下的隐藏文件(.开头的),最终我写了一个shell函数来判断一下sqoop目录是否包含非隐藏文件,有文件的话再进行hive load:

思路就是hdfs列出目录下的文件,basename取文件名并忽略掉.开头的隐藏文件,最后统计一下文件个数。

那么再封装一下hive load函数,方便自动忽略空目录:

使用时传入hdfs路径和load命令:

这就是我暂时遇到的sqoop坑,有其他经验后续会继续分享。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~