GaussDB(DWS)迁移 -数据迁移 - 使用Spark的scala接口往GaussDB(DWS)导入数据失败分析
【问题场景】
某局点使用Spark的scala接口从hive往GaussDB(DWS)进行大批量数据导入的时候,必然出现下面的报错导致数据导入任务失败
导数脚本如下:
scala> val df = spark.read.table("CTE_REP.TEST").limit(1000000).repartition(6).toDF()
scala> df.write.mode("Overwrite").jdbc("jdbc:postgresql://10.60.178.127:25308/postgres", "CTE_REP.test", t1)
同时客户确认相同的程序从hive往PostgesSQL导入数据的时候没有出现此类异常
【问题原因】
使用Spark的scala接口进行抽数操作时,如果数据源端的表定义中某个字段的数据类型和数据目标端的表定义中对应字段的数据类型不匹配,那么Spark代码逻辑在处理NULL值和非NULL值时可能会出现逻辑冲突。具体为
- 对于非null值,使用函数makeSetter绑定参数值
- 对于null值,调用 getJdbcType来绑定null值
从上面代码可以看到函数getJdbcType和makeSetter都有一个入参dialect,这里的dialect(方言,这里的意思是要写入的数据库,比如Oracle、PostgreSQL、db2、teredata等)跟底层参数值的bind逻辑强相关。而出问题的集群上使用GaussDB(DWS)的驱动为gsjdbc200.jar,不在Spark内部注册的dialect范围(当前Spark内置注册的dialect有db2、mysql、oracle、teredata、postgresql、SqlServer、Derby)内。当同一个batch的数据的某个字段同时出现了null值和非null值的时候,会导致同一个字段bind走进了两个代码分支,有可能bind了两个不同的数据类型,触发了PreparedStatement的两次parse(具体原因需要看jdbc源码,此处未深入走读),导致出现这个问题。
当Spark不识别驱动对应的dialect时,getJdbcType底层调用为函数getCommonJDBCType, 而CLOB在驱动gsjdbc200.jar中又被识别为OID,从而导致发生如上现象
对于db2、mysql、oracle、teredata、postgresql、SqlServer、Derby这几种Spark内置支持的几种dialect,Spark代码中进行了特殊处理,在某些特性场景下会规避此类问题,降低问题出现的概率。
Spark对于Oracle的特殊处理如下:
Spark对于PostgreSQL的特殊处理如下:
【触发因素】
针对上述分析,结合现网场景进行排查,确定现网场景下具体触发因素如下
- 数据源端的hive中表test的字段 report_dt的数据类型是string
- 数据目标端GaussDB(DWS)中表test的字段 report_dt的数据类型是timestamp
- 同一个批次导入的数据中,表test的report_dt字段出现了NULL值和非NULL值
- GaussDB(DWS)使用的驱动包是gsjdbc200.jar,Spark无法识别此方言
【处理建议】
- 保证源端和目标端的表定义的字段顺序和数据类型一致
【附-GaussDB(DWS)驱动说明】
GaussDB(DWS)提供gsjdbc4.jar和gsjdbc200.jar两个驱动,它们的区别在于
- gsjdbc4.jar:与PostgreSQL保持兼容的驱动包,其中类名、类结构与PostgreSQL驱动完全一致
- gsjdbc200.jar:如果同一JVM进程内需要同时访问PostgreSQL及GaussDB(DWS),请使用此驱动包。它的主类名为“com.huawei.gauss200.jdbc.Driver”(即将“org.postgresql”替换为“com.huawei.gauss200.jdbc”),数据库连接的URL前缀为“jdbc:gaussdb”,其余与gsjdbc4.jar相同
- 点赞
- 收藏
- 关注作者
评论(0)