当前位置:C++技术网 > 精选软件 > mysql数据精度丢失问题深入探讨

mysql数据精度丢失问题深入探讨

更新时间:2016-09-20 10:41:45浏览次数:1+次

    我们知道,mysql存储小数可以使用float、double、decimal等。这些类型存储的小数精度都比较高。我们平时应用最多的就是两位小数点,所以,这些类型都是可以满足的。
    那么数据精度丢失是如何表现的呢?到底是什么原因呢?
    当我们不知道原因的时候,总会认为到处都是问题,找不到关键点。在应用的时候,总是不踏实,唯唯诺诺的。搞清楚缘由,找到问题的关键点,然后加以深入理解,方可给自己一个定心丸。
    精度变化不管是哪种类型,都会发生。网上言传float、double精度可能丢失,decimal精度不会丢失,所以建议decimal来存储金额值。但是,没有指明确切缘由,只是大概是这么回事,其实离真相还差得远。
    在mysql中,我们用【小数数据类型(总长度,小数点长度)】来表示小数的总长度和小数点后面的长度,如decimal(m,n)。n就是小数点后面的数字个数。float(m,n)、double(m,n)含义差不多,都是定义长度和精度的。既然都定义了精度,为什么还会发生所谓的精度丢失问题呢?
    查阅网上的说法,要么是没有设置精度位数,要么就是设置的精度和存储时的精度不一致,要么就是mysql存的,用其他数据库引擎查询。基本是这三类可以概括各种情况。
    这三种有什么共性和差异性呢?如果能够将这三种情况抽象为一个共同的模型,我们就找到了精度丢失问题的关键。而不是盲人摸象般的局部就事论事,以偏概全了。
    没有设置精度就是使用默认的精度,此时的策略就是,尽可能保证精度,因此一般使用最高精度存储数据的。如果设置数据类型指定了精度,那么存储数据时就按照设置的精度来存储。第三种暂时不讨论,先把前面两种说完。
    假设默认精度为6个小数位或设置为6位,我们存储小数一般是:2 1.1 2.11 32.214 41.4513 5.21452 6.214522 7.1421457,这里有8个不同的小数位的数,在存储到数据库时会发生什么问题?
    使用默认精度,数据库用6位来存储。所以前面6个数字,精度都不够6位。当然,存储时如果是float和double,那么会尽可能以近似的值存储,以保证精度。所以,结果可能是:2.000001 2.110005 32.213999 41.451301 5.214519。如果用的是decimal,则结果是:2 1.1 2.11 32.214 41.4513 5.21452 。从这个结果可以看到,decimal的存储结果没有精度丢失问题。因为decimal内部以字符形式存储小数,属于准确存储。而float和double等则属于浮点数数字存储,所以没有办法做到准确,只能尽可能近似。这也是大家选用decimal的原因,也认为decimal精度不会丢失的原因。
    这个结果对,但不全对。数据的精度丢失发生在存储和获取这一瞬间。所以,存储和获取这两个方面也就是精度发生变化的时刻。这里的精度丢失发生在存储时。这里举例使用6位精度来兼容不同的精度数值。还有6位精度和大于6位精度的值我们还没有讨论,现在来讨论一下。
    是不是6位精度值存入6位精度类型精度就不会丢失了呢?必须不能丢失。如果丢失了,谁还敢用这样的类型呢?这样的类型还有什么意义呢?所以,6.214522存入6位小数的float和double是不会丢失小数精度的,取出来的数还是6.214522。
    也就是说,一个小数存入相同的精度的数据类型时,精度是不会丢失的。
    另一种情况,当7或更多位精度的数字存入6位精度类型字段时,会发生什么?结果会发生四舍五入。四舍五入的结果就是匹配字段的数据类型的精度长度。此时精度也会丢失。不管内部如何处理,我们得到的数据是经过四舍五入的。但是有一点可以确定,我们在读取取舍后的数字时,是固定的。虽然浮点数存储的不是确切的数值,但是在你指定的精度长度条件下,存取都是确定的一个数值。而发生精度变化的就是数值的精度和字段的精度长度不匹配,从而发生数值扩展精度和截断精度问题,这也就是浮点数精度不准确的问题。
    有人说,用浮点数存储在查询时会发生无法匹配的问题。例如,存入的数据为15.21,查询的时候用15.21竟然匹配不到15.21这条记录。然而这是个人没有理解数据库浮点数处理问题。我反正是可以准确匹配查询的。在设置两位精度的字段数据类型时,存取数据得到的都是两位精度的值,在查询时用两位的数值自然也是可以匹配到的。匹配不到的情况是发生在精度不对等的情况,就是前面讨论的扩展了精度和截断了精度的情况。这本身就是数据库使用不正确的问题,并不能说明float和double不能用在金额上的存储。然而网上并没有人对此深入思考过。不是转载就是重复同一个观点,缺乏思考。
    而第三个问题,mysql数据库使用其他数据库引擎来查询,得到的结果会发生精度丢失。这个精度丢失的原因,就可能是不同的数据库引擎对浮点数的精度扩展和截断处理策略不一致,而且,存储时策略也不一致。所以导致精度会出现各种变化。这种问题也就是催生decimal类型的出现。我们前面看到的decimal是可以确切存储小数的精度的。因为在存储的时候会将小数以字符串存储,就不会再发生精度的扩展问题。但是decimal依然会发生精度截断问题。如果decimal指定精度为2位小数,存入的是这样的值:12.123,你觉得结果如何?当然还是会发生四舍五入。结果就是12.12,然而12.12以字符串形式存入了数据库。此后,12.12始终都是12.12,表现出来的是小数,然而内部是字符串形式存储,所以,小数精度不会再发生变化了。我们不管以什么精度来获取这个值,都是12.12,而且,不管是一般数据库引擎读取到的也都是12.12,所以decimal才是大家推荐使用的金额存储类型。
    我们应该知道,数据库作为中心,驱动着各种软件的发展,然而在实现上,我们可能用Java方式来访问MySQL,也可能是.Net方式访问mysql,不同的语言的mysql引擎是有差别的。但是不管怎么样,decimal类型都是可以确定精度的,不会发生四舍五入。所以在各个软件中都表现良好。
    所以,不要盲目的说float和double精度可能发生丢失,而是说在存取时因为精度不一致会发生丢失,当然这里的丢失指的是扩展或者截断了,丢失了原有的精度。decimal是好,但不是说不会发生任何精度丢失。如果问题看得不深入,总会以偏概全。decimal占用的字节多。不过在计算的时候,数据是准确的。而float和double在计算前一定要对取到的数据的精度做处理,确保和存储的精度一致。而存储的精度也和需要的精度一致,不要长也不要短。