MySQL与Apache Spark编码差异排查:productCode去重结果不一致问题
遇到过一模一样的问题!核心原因其实是MySQL的utf8mb4和JDBC驱动默认的utf8编码不兼容——MySQL的utf8是阉割版的3字节UTF-8,而utf8mb4才是完整的4字节UTF-8(支持emoji、特殊符号这些),但JDBC驱动里的characterEncoding=utf8参数对应的是MySQL的utf8,不是utf8mb4,这就导致4字节字符被错误转义或截断,最终Spark里的字符串和MySQL源数据不一致,去重结果自然对不上。
下面给你一步步排查和解决:
第一步:确认JDBC驱动版本
MySQL JDBC驱动在5.1.38版本之后才完整支持utf8mb4,如果你的驱动版本低于这个,先升级!比如启动PySpark时指定依赖:
pyspark --packages mysql:mysql-connector-java:8.0.33
如果用的是Databricks这类托管环境,直接在库管理界面添加最新的MySQL驱动即可。
你也可以在PySpark代码里验证当前驱动版本:
from py4j.java_gateway import java_import java_import(spark._jvm, "com.mysql.cj.jdbc.Driver") print(spark._jvm.com.mysql.cj.jdbc.Driver.getVersion())
第二步:修改JDBC连接串的编码参数
把原来的characterEncoding=utf8改成characterEncoding=utf8mb4,同时加上connectionCollation参数匹配你数据库的排序规则(比如你的库用utf8mb4_unicode_ci,就设置对应值)。修改后的连接串示例:
dburl = "jdbc:mysql://localhost:3306/dbname?useServerPrepStmts=false&rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8mb4&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false&connectionCollation=utf8mb4_unicode_ci"
这里的connectionCollation很关键,它会让JDBC连接使用和数据库一致的字符比较逻辑,避免因为排序规则不同导致去重差异。
第三步:定位具体差异的字符串
行数一致但去重值不同,说明某些字符串在Spark里被错误处理了。你可以先在MySQL里找出包含4字节字符的行:
SELECT productCode, HEX(productCode) FROM your_table WHERE CHAR_LENGTH(productCode) != LENGTH(productCode);
这个SQL会筛选出所有包含多字节字符(比如emoji、特殊符号)的记录——因为CHAR_LENGTH是字符数,LENGTH是字节数,4字节字符的字节数是字符数的4倍。
然后在Spark里查询这些字符串的十六进制值,对比和MySQL的差异:
from pyspark.sql.functions import hex # 替换成你从MySQL查到的有问题的字符串 problem_codes = ["xxx", "yyy"] df.filter(df.productCode.isin(problem_codes)).select("productCode", hex("productCode")).show(truncate=False)
如果十六进制值不一样,说明编码转换出了问题,升级驱动+修改连接串就能解决。
第四步:处理排序规则导致的去重差异
如果编码没问题,但去重数还是不一致,那大概率是排序规则的锅。比如MySQL的utf8mb4_unicode_ci会把某些相似字符视为相等(比如ß和ss),但Spark默认按字节比较,所以去重结果不同。
这时候你可以在Spark里用collate函数匹配MySQL的排序规则:
from pyspark.sql.functions import expr # 替换成你数据库实际使用的排序规则 distinct_count = df.select(expr("productCode collate utf8mb4_unicode_ci")).distinct().count() print(distinct_count)
如果这个结果和MySQL的去重数一致,后续处理就统一使用这个排序规则进行去重即可。
我之前就是因为驱动版本老旧+编码参数不对,导致emoji字符被截断,去重数差了好几百,按照上面的步骤调整后就完全正常了。
内容的提问来源于stack exchange,提问作者Neron Joseph

