0886-7.1.7-Hive1与Hive3中Decimal数据计算结果精度缺失问题分析(hive decimal精度丢失)?

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集群分别创建相同的表,并初始化相同的数据到表中。

create table decimal_test(a decimal(,), b decimal(,), c decimal(,));
    

向表中插入测试数据:

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:

select -1*c from decimal_test;
    

CDP集群显示结果如下:

CDH集群显示结果如下:

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而导致数据在计算的过程中丢失了精度。