1.文档编写目的
将集群从CDH升级到CDP后,Hive1与Hive3在Decimal精度的处理上发生了变化,导致两个版本的Hive在进行Decimal类型的数据计算时存在差异,主要体现在计算结果精度缺失。本篇文章主要从Hive1和Hive3对Decimal类型的处理上进行分析,进而详细解释精度缺失的原因。
Decimal数据类型用于要求非常精确的计算,而 Decimal数据类型允许将数值和计算方法指定为选择参数。在这里精度指的是为这个值保存的有效数字的总数,计数法是指数字在小数点之后的数目。比如,decimal (5,2)规定存储的值不会超过5位数字,并且在小数点后有两个数字。
- 测试环境说明
1.CDH5.16.2
2.CDP7.1.7
2.测试场景准备
在CDH和CDP集群分别创建相同的表,并初始化相同的数据到表中。
代码语言:javascript复制create table decimal_test(a decimal(,), b decimal(,), c decimal(,));
向表中插入测试数据:
代码语言:javascript复制insert into decimal_test values(321.333,4.123,1324.855959);
insert into decimal_test values(1.333,1.123,0.12345678912345);
CDP集群查询结果显示如下:
CDH集群查询结果显示如下:
可以看出在CDP中对Decimal类型的精度要求更严格,在小数位不足时会自动补零,以确保数据的精确性。
3.问题现象
在两个集群分别执行如下两个场景SQL1和SQL2:
代码语言:javascript复制select -1*c from decimal_test;
CDP集群显示结果如下:
CDH集群显示结果如下:
代码语言:javascript复制select a*b*c from decimal_test;
CDP集群显示结果如下:
CDH集群显示结果如下:
计算结果汇总:
SQL | CDH5.16.2 | CDP7.1.7 |
---|---|---|
SQL1(-1*c) | -1324.855959 | -1324.855959000000 |
-0.12345678912345 | -0.123456789123 | |
SQL2(a*b*c) | NULL | 1755243.312098 |
0.18480975158945058855 | 0.184810 |
可以看到CDH和CDP中Hive对Decimal类型数据进行计算时,产生的最终结果存在一定的精度缺失现象。
4.Decimal计算源码分析
在弄清楚Hive处理Decimal数据精度缺失问题上,有必要先熟悉下Hive1和Hive3之间Decimal类型的处理逻辑的差异,在org.apache.hadoop.hive.ql.udf.generic.
GenericUDFOPMultiply类中,可以查看到具体的处理逻辑。
CDH5.16.2版本中Hive的Decimal类型处理逻辑如下截图:
Decimal精度的计算方式比较简单粗暴,两个Decimal类型的数据在进行计算时,precision和scale的值主要是通过传入两个Decimal类型的prec、scale分别进行相加与38相比取最小值,最终得出一个新的Decimal类型。如:decimal(38,14) 与 Decimal(38,14),最终计算的结果为Decimal(38,28)。
CDP7.1.7版本中Hive的Decimal类型处理逻辑:
在CDP的Hive中Decimal的精度计算方式发生了变化,在做精度相加的计算后,还增加了adjustPrecScale的方法,当输入的precision超出了最大值38,则需要对精度进行校准,以确保两个Decimal不会因为精度超出最大值38,造成精度溢出的问题,如下示例:
表格原始数据如下:
a [decimal(38,14)] | b [decimal(38,14)] | c [decimal(38,14)] |
---|---|---|
321.333 | 4.123 | 1324.855959 |
1.333 | 1.123 | 0.12345678912345 |
- SQL1场景分析
SQL | CDH5.16.2 | CDP7.1.7 |
---|---|---|
SQL1(-1*c) | -1324.855959 | -1324.855959000000 |
-0.12345678912345 | -0.123456789123 |
在SQL1场景中,可以看到字段c的值在乘以-1后,返回的Decimal数值在CDH集群中与预期的结果一致,而在CDP集群中返回的结果精度缺失了2位。
在进行精度计算时,会将-1转化为Decimal(1,0)数据类型,根据Hive1中精度计算公式可以得出-1[decimal(1,0)] * c[decimal(38,14)] = r[decimal(min(38,1 38 1),min(14 0,38))] = r[decimal(38,14)] 结果数据的精度是没有发生变化,因此得出的结果与预期一致;而根据Hive3中的精度计算公式可以得出
-1[decimal(1,0)] * c[decimal(38,14)] = r[Decimal(38, max((38-(1 38 1 - 0 -14), min(0 14,6))))] = r[decimal(38,12)],最终计算后的精度为Decimal(38,12),因此可以看到在CDP集群中计算后精度缺失2位。
- SQL2场景分析
SQL | CDH5.16.2 | CDP7.1.7 |
---|---|---|
SQL2(a*b*c) | NULL | 1755243.312098 |
0.18480975158945058855 | 0.184810 |
在SQL2场景中,三个Decimal类型的数据相乘时得到的结果在CDH的Hive1中结果出现了NULL,而在CDP的Hive3中没有输出异常的NULL,但数据的精度再次出现了缺失的现象。
由于精度计算的方法只支持两个Decimal类型的参数,因此在进行超过2个Decimal数据的计算时,会将计算公式进行拆分为((a * b)* c),此时根据这种原则再次套用Hive1的计算公式可以得出:
a[decimal(38,14)] * b[decimal(38,14)] = m[decimal(min(38,38 38 1),min(14 14,38))]
= m[decimal(38,28)]
m[decimal(38,28)]* c[decimal(38,14)] = r[decimal(min(38,38 38 1),min(28 14,38))]
=r[decimal(38,38)]
根据计算得出最终数值的精度为Decimal(38,38),因为precision和scale数值一致该精度则表示计算的最终结果必须小于0,如果大于0的数值,则会导致转换失败显示为NULL的问题,也就是我们上述得到的结果。
Hive3的计算公式可以得出:
a[decimal(38,14)] * b[decimal(38,14)] = m[Decimal(38, max((38-(38 38 1 - 14 -14), min(14 14,6))))] =m[Decimal(38,6)]
m[Decimal(38,6)] * c[decimal(38,14)] = r[Decimal(38, max((38-(38 38 1 - 6 -14), min(6 14,6))))] = r[Decimal(38,6)]
可以看到最终计算出的数据精度为Decimal(38,6),也与最终得到的结果显示的一致,这也就很好的解释了CDH与CDP中计算出来的结果存在一定的差异了。
6.总结
1.在CDP集群中Hive对Decimal类型要求更为严格,在精度不足的情况下会自动补零显示。
2.在CDH的Hive中Decimal类型计算比较简单粗暴,当prec和scale定义的比较大时,在进行计算时会出现precision和scale一致的情况,此时会造成大于0的计算结果返回NULL的现象。
3.CDP集群中的Hive在进行Decimal类型计算时,通过重新校准精度的方式来避免精度溢出而出现异常的计算数据(比如:Hive1中显示的NULL问题)。
4.选择合适的精度来定义自己的Decimal数据,以避免因为过大的设置precision和scale而导致数据在计算的过程中丢失了精度。
5.如果超过两个Decimal数据进行计算时,会自动的根据加减乘除的规则将计算公式拆分,因为deriveResultDecimalTypeInfo方式只支持传入两个Decimal类型进行校准,具体的拆分可以根据Hive的执行计划来查看。