PySpark使用Spark MSSQL连接器BulkCopy时报NoSuchMethodError
问题背景
使用MS SQL connector for Spark从PySpark向SQL Server插入大批量数据时抛出方法不存在错误。
SparkSession初始化配置
SparkSession.builder .config('spark.jars.packages', 'org.apache.hadoop:hadoop-azure:3.2.0,org.apache.spark:spark-avro_2.12:3.1.2,com.microsoft.sqlserver:mssql-jdbc:8.4.1.jre8,com.microsoft.azure:spark-mssql-connector_2.12:1.2.0')
报错堆栈
ERROR executor.Executor: Exception in task 6.0 in stage 12.0 (TID 233) java.lang.NoSuchMethodError: 'void com.microsoft.sqlserver.jdbc.SQLServerBulkCopy.writeToServer(com.microsoft.sqlserver.jdbc.ISQLServerBulkData)' at com.microsoft.sqlserver.jdbc.spark.BulkCopyUtils$.bulkWrite(BulkCopyUtils.scala:110) at com.microsoft.sqlserver.jdbc.spark.BulkCopyUtils$.savePartition(BulkCopyUtils.scala:58) at com.microsoft.sqlserver.jdbc.spark.SingleInstanceWriteStrategies$.$anonfun$write$2(BestEffortSingleInstanceStrategy.scala:43) at com.microsoft.sqlserver.jdbc.spark.SingleInstanceWriteStrategies$.$anonfun$write$2$adapted(BestEffortSingleInstanceStrategy.scala:42) at org.apache.spark.rdd.RDD.$anonfun$foreachPartition$2(RDD.scala:1020) at org.apache.spark.rdd.RDD.$anonfun$foreachPartition$2$adapted(RDD.scala:1020) at org.apache.spark.SparkContext.$anonfun$runJob$5(SparkContext.scala:2236) at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:90) at org.apache.spark.scheduler.Task.run(Task.scala:131) at org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$3(Executor.scala:497) at org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1439) at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:500) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:829)
数据写入代码
try: ( df.write.format("com.microsoft.sqlserver.jdbc.spark") .mode("append") .option("url", url) .option("dbtable", table_name) .option("user", username) .option("password", password) .option("schemaCheckEnabled", "false") .save() ) except ValueError as error: print("Connector write failed", error)
已尝试操作
- 更换多个Spark版本、SQL连接器版本,问题未解决
- 直接指定本地mssql-jdbc依赖jar包路径:
SparkSession.builder .config('spark.jars', '/mssql-jdbc-8.4.1.jre8.jar') .config(...)
仍提示找不到对应方法,检查JDBC jar包源码可确认该方法已定义。
排查方案
该NoSuchMethodError为典型的类版本冲突问题,与业务代码逻辑无关,按以下优先级排查:
- 修复类加载顺序冲突
核心原因是Spark运行环境(集群节点预装jar、Spark默认jars目录、其他依赖传递引入)存在旧版本mssql-jdbc,类加载器默认优先加载旧版本jar,旧版本无writeToServer(ISQLServerBulkData)方法,即使通过spark.jars指定正确版本也不会被优先加载。
处理操作:- 新增两项Spark配置:
spark.driver.userClassPathFirst = true、spark.executor.userClassPathFirst = true,强制JVM优先加载用户指定的jar包,覆盖Spark自带依赖的加载优先级 - 逐台检查所有Spark节点的
$SPARK_HOME/jars目录、Hadoop公共lib目录,删除所有版本不匹配的mssql-jdbc相关jar,避免隐式引入冲突
- 新增两项Spark配置:
- 严格匹配官方兼容版本
com.microsoft.azure:spark-mssql-connector_2.12:1.2.0官方编译时绑定的mssql-jdbc版本为8.2.2.jre8,随意升级JDBC版本到8.4.1会出现跨小版本方法签名不兼容问题,直接导致连接器调用失败。将依赖中的mssql-jdbc版本替换为官方绑定的8.2.2.jre8即可:SparkSession.builder .config('spark.jars.packages', 'org.apache.hadoop:hadoop-azure:3.2.0,org.apache.spark:spark-avro_2.12:3.1.2,com.microsoft.sqlserver:mssql-jdbc:8.2.2.jre8,com.microsoft.azure:spark-mssql-connector_2.12:1.2.0') - 排查传递依赖冲突
如果项目引入过其他第三方包,可能通过依赖传递引入不兼容版本的mssql-jdbc,提交任务时通过verbose参数打印依赖树排查:
定位到引入错误版本的依赖后,通过exclude规则排除对应冲突依赖即可。spark-submit --verbose [任务其他提交参数] 2>&1 | grep mssql-jdbc - 验证Jar实际加载版本
在执行写入逻辑前增加调试代码,打印运行时实际加载的SQLServerBulkCopy类的来源jar路径,确认加载的是指定版本:
若打印出的jar路径不是指定的8.2.2版本,说明仍存在类加载优先级问题未处理干净。import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy print(SQLServerBulkCopy.getProtectionDomain().getCodeSource().getLocation())
内容的提问来源于stack exchange,提问作者warreee
相关产品推荐
相关产品推荐

