UFIV
问题
业务反馈看板的数据存在缺失,查看看板对应的数据链路,发现中间有一张 dws 表数据缺失了,看该表的任务是正常执行。比对任务脚本 SQL 及执行日志发现,原因是出在一个条件判断,业务逻辑上我们需要过滤出 uid > 10000 的正常数据,但查询的数据底表刚好 uid 字段为 string 类型。在 spark 中优化后的 SQL 却变成了 CAST(uid AS INT) > 10000,而 uid 在数值范围上是有可能大于 2^31-1 的,这一部分超出 int 范围的数据因为转换后溢出成负数而使条件判断为 false 被过滤掉。
select
*
from
table_t
where
date_par = 19700101
and uid > 10000
类型转换的逻辑
这里我们可以看一下 spark 中涉及到类型隐式转换的几个处理规则。
- 在 IN 条件判断中,将每个元素转换成同一更宽类型
findWiderCommonType - 表间的 UNION、INTERSECT、DISTINCT 集合操作同样为
findWiderCommonType - 算术表达式中的 string 类型转换(含比较操作):
- 数据类的计算统一转换成 double,比如
1 + '1234',sum(col@string) - 时间类型的判断统一转换成 string,比如
2022-01-01@DATE = col@string
- 数据类的计算统一转换成 double,比如
- decimal 间的操作,优先取最大的 scale 和最小的 precision(和整数类型的字段比较不存在精度问题)
- bool 等值比较,转成与非 bool 值字段相同类型(只限于数值类型),true 为 1,false 为 0。
- case when then 与 else 分支的类型转换
findWiderCommonType/foldLeft(NULL)(findWiderTypeForTwo) - 在 if 条件判断中的类型转换
findWiderTypeForTwo - 与期望类型的转换,在函数中我们往往会传一些非函数签名类型的参数,在 spark analyzer 中会尝试将所给的参数向目标类型转换。
findWiderTypeForTwo 规则:
- decimal 与整数类型转化成 decimal
- decimal 与浮点数类型统一转化成 double
- 数字类型按 byte -> short -> integer -> long -> float -> double 优先级提升
- 时间类型统一转化为 timestamp
- 当类型中有 string 而其它非 bool 和 binary 的原子类型时,统一转化为 string
当无法满足上述转换规则则报错。
结论
实际上按照上述转换规则 uid 字段应该是转换成 double 才对,拉取代码到本地验证也确实如此,这里猜测不一致应该是线上运行的框架有加载其它的规则或改动。
当然这里只讨论 spark 的转换,每个引擎都有各自的一套逻辑,规则存在差异,特别是现在平台都把所有执行引擎封装在背后,因此不要期望我们直觉所想的在任务中会得到我们期望的结果,避免这类问题的最好方法就是不要让它发生:
- 对于业务同一个字段始终统一类型,并在底层处理好。
- 需要对两个不同类型的字段做操作时,显式地使用 cast 转换。