Sqoop使用字符串字段作为--split-by参数报错,求解决方案
--split-by 使用字符串字段抛出异常的问题 我来帮你分析下问题根源并给出可行的解决方案:
问题本质
Sqoop的--split-by参数是用来为多个Map任务划分数据分片的,它默认只支持数值类型的字段。这是因为Sqoop需要基于字段的连续数值范围切割数据(比如从最小值到最大值拆分成N份),而字符串类型的字段无法生成这种连续、可均匀分割的区间,所以直接用字符串字段DIV_VALUE就会触发异常。
解决方案
根据你的场景,我整理了几种优先级从高到低的解决办法:
1. 优先使用数值型字段作为--split-by(最优方案)
如果目标表${qualifier}.DTL中存在数值类型的主键、唯一键或者分布均匀的数值字段(比如自增ID、数值格式的日期字段等),直接替换--split-by为这个字段即可。比如假设表中有ACCOUNT_ID(数值型主键),修改后的命令如下:
sqoop import -Ddb2.jcc.sslConnection=true \ --connect jdbc:db2://192.1.1.2:6060/DB2M \ --username ${username} \ --password $password \ --query " SELECT ACCOUNT_DATE,DIV_VALUE from ${qualifier}.DTL where year = '${year}' AND SUBSTR(LOSS_TRAN,1,1) NOT IN ('1','9') and \$CONDITIONS " \ -m 500 \ --split-by "ACCOUNT_ID" \ --fields-terminated-by '|' \ --target-dir s3://test${env}/${year}
这种方式能保证分片最均匀,导入效率最高。
2. 用哈希函数将字符串转为数值型分片(次优方案)
如果没有合适的数值型字段,可以利用DB2的HASH()函数将字符串字段DIV_VALUE转换为整数,以此作为分片依据。这样Sqoop就能基于哈希值的数值范围均匀分割任务,命令修改如下:
sqoop import -Ddb2.jcc.sslConnection=true \ --connect jdbc:db2://192.1.1.2:6060/DB2M \ --username ${username} \ --password $password \ --query " SELECT ACCOUNT_DATE,DIV_VALUE from ${qualifier}.DTL where year = '${year}' AND SUBSTR(LOSS_TRAN,1,1) NOT IN ('1','9') and \$CONDITIONS " \ -m 500 \ --split-by "HASH(DIV_VALUE)" \ --fields-terminated-by '|' \ --target-dir s3://test${env}/${year}
DB2的HASH()函数会将字符串映射为整数,Sqoop可基于这个整数值的范围拆分数据,能保证各个Map任务的负载相对均衡。
3. 强制使用单Map任务(适合小数据量)
如果你的数据量不大,不需要并行导入,可以直接添加--autoreset-to-one-mapper参数,强制Sqoop只用1个Map任务,这样就不需要指定--split-by了:
sqoop import -Ddb2.jcc.sslConnection=true \ --connect jdbc:db2://192.1.1.2:6060/DB2M \ --username ${username} \ --password $password \ --query " SELECT ACCOUNT_DATE,DIV_VALUE from ${qualifier}.DTL where year = '${year}' AND SUBSTR(LOSS_TRAN,1,1) NOT IN ('1','9') and \$CONDITIONS " \ --autoreset-to-one-mapper \ --fields-terminated-by '|' \ --target-dir s3://test${env}/${year}
这种方法最简单,但缺点是没有并行处理,导入速度较慢,仅适合数据量较小的场景。
4. 自定义字符串范围分片(不推荐)
你也可以通过--boundary-query指定字符串字段的最小/最大值,让Sqoop按字典序分割字符串区间,但这种方式极易出现分片数据量不均衡的情况(比如某些前缀的字符串数量远多于其他前缀),除非你明确知道字符串分布非常均匀,否则不建议使用:
sqoop import -Ddb2.jcc.sslConnection=true \ --connect jdbc:db2://192.1.1.2:6060/DB2M \ --username ${username} \ --password $password \ --query " SELECT ACCOUNT_DATE,DIV_VALUE from ${qualifier}.DTL where year = '${year}' AND SUBSTR(LOSS_TRAN,1,1) NOT IN ('1','9') and \$CONDITIONS " \ -m 500 \ --split-by "DIV_VALUE" \ --boundary-query "SELECT MIN(DIV_VALUE), MAX(DIV_VALUE) FROM ${qualifier}.DTL where year = '${year}' AND SUBSTR(LOSS_TRAN,1,1) NOT IN ('1','9')" \ --fields-terminated-by '|' \ --target-dir s3://test${env}/${year}
总结
优先选择方案1或2,这两种方式能保证导入的效率和分片均衡性;如果数据量小可以用方案3;方案4仅在字符串分布极其均匀的情况下考虑。
内容的提问来源于stack exchange,提问作者Teju Priya

