Spark 3.5.0中MySQL ENUM列WHERE条件UPPER函数失效问题
Spark 3.5.0 升级后 MySQL ENUM 字段 UPPER 过滤失效问题及无代码修改解决方案
问题背景
Spark作业从3.3.1升级至3.5.0后,查询MySQL数据库时,UPPER(col) = UPPER(value)形式的过滤条件失效。涉及的WHERE子句为:
UPPER(vn) = 'ERICSSON' AND (upper(st) = 'OPEN' OR upper(st) = 'REOPEN' OR upper(st) = 'CLOSED')
其中st列是MySQL的ENUM类型,这是问题的核心根源。
物理计划对比
Spark 3.3.1 版本Filter阶段
+- Filter ((upper(vn#11) = ERICSSON) AND (((upper(st#42) = OPEN) OR (upper(st#42) = REOPEN)) OR (upper(st#42) = CLOSED)))
Spark 3.5.0 版本Filter阶段
+- Filter ((upper(vn#11) = ERICSSON) AND (((upper(staticinvoke(class org.apache.spark.sql.catalyst.util.CharVarcharCodegenUtils, StringType, readSidePadding, st#42, 13, true, false, true)) = OPEN) OR (upper(staticinvoke(class org.apache.spark.sql.catalyst.util.CharVarcharCodegenUtils, StringType, readSidePadding, st#42, 13, true, false, true)) = REOPEN)) OR (upper(staticinvoke(class org.apache.spark.sql.catalyst.util.CharVarcharCodegenUtils, StringType, readSidePadding, st#42, 13, true, false, true)) = CLOSED)))
问题根源
Spark 3.4.0新增spark.sql.readSideCharPadding配置项,默认值为true。该配置触发CharVarcharCodegenUtils类的readSidePadding方法,对读取的字符串进行空格填充:
public static UTF8String readSidePadding(UTF8String inputStr, int limit) { int numChars = inputStr.numChars(); if (numChars == limit) { return inputStr; } else if (numChars < limit) { return inputStr.rpad(limit, SPACE); } else { return inputStr; } }
该方法错误地对ENUM类型值执行空格填充,导致UPPER(st)处理后的值带空格,无法与目标字符串匹配。
移除UPPER函数后的现象
去掉WHERE条件中的UPPER函数后,Filter阶段物理计划显示填充空格的值能正常匹配,查询返回正确结果:
+- Filter (((staticinvoke(class org.apache.spark.sql.catalyst.util.CharVarcharCodegenUtils, StringType, readSidePadding, st#42, 13, true, false, true) = OPEN ) OR (staticinvoke(class org.apache.spark.sql.catalyst.util.CharVarcharCodegenUtils, StringType, readSidePadding, st#42, 13, true, false, true) = REOPEN )) OR (staticinvoke(class org.apache.spark.sql.catalyst.util.CharVarcharCodegenUtils, StringType, readSidePadding, st#42, 13, true, false, true) = CLOSED ))
修改spark.sql.readSideCharPadding的测试结果
将spark.sql.readSideCharPadding设为false后,出现两种情况:
- 保留UPPER函数:过滤器不下推至MySQL,查询正常运行,物理计划如下:
+- Filter (((upper(st#42) = OPEN) OR (upper(st#42) = REOPEN)) OR (upper(st#42) = CLOSED))
- 移除UPPER函数:过滤器下推至MySQL,但条件值带空格,无法获取数据:
PushedFilters: [*IsNotNull(vn), *EqualTo(vn,ERICSSON), *Or(Or(EqualTo(st,OPEN ),EqualTo(st,REOPEN )),EqualTo(st,CLOSED ))]
无代码修改的解决方案
方案1:针对ENUM字段单独禁用填充
Spark 3.5.0支持为特定列设置字符填充规则,可通过JDBC选项或全局配置为st列禁用读侧空格填充:
- 读取MySQL数据时添加JDBC参数:
.option("spark.sql.readSideCharPadding.st", "false")
- 或在Spark全局配置中添加:
spark.sql.readSideCharPadding.st=false
此方案既保留其他列的默认填充行为,又避免ENUM字段被错误填充。
方案2:全局禁用填充+关闭过滤器下推
如果不需要过滤器下推至MySQL,可全局设置:
spark.sql.readSideCharPadding=false spark.sql.pushDownFilters=false
所有过滤器将在Spark端处理,既避免ENUM字段填充空格,也不会出现下推时的空格匹配问题。
方案3:SQL层面添加TRIM(无需修改业务代码,仅调整SQL模板)
若允许通过配置化方式修改SQL,可在UPPER前添加TRIM去除填充空格:
UPPER(vn) = 'ERICSSON' AND (upper(TRIM(st)) = 'OPEN' OR upper(TRIM(st)) = 'REOPEN' OR upper(TRIM(st)) = 'CLOSED')
内容的提问来源于stack exchange,提问作者Suyash Ajmera
相关产品推荐
相关产品推荐

