最近做工厂项目,测试提的一个bug在本地测了好久一直没复现;直接连测试线的数据库,又经过一系列流程模拟最终在本地复现了这个问题;由于有消息日志的定时输出,只能打点介入来追踪bug,最后发现问题出在对double列累加的SQL语句上,真是一顿好找。

百度‘mysql 设置double列累加’,发现精度缺失对于double类型是不可控的。网上都建议将表字段更换为decimal类型,对于上线已久或开发完成的项目而言,显然是不合适的,因为改了列的类型又会引发列取值问题。

由此,我根据本地已复现的bug做了如下测试;

新建一张表:

CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `numDouble` double NOT NULL COMMENT 'double类型取值',
  `numDoubleLimit` double DEFAULT NULL COMMENT 'double类型精确取值',
  `numDecimal` decimal(10,5) NOT NULL COMMENT 'decimal精确取值',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

先插入一条数据:

INSERT INTO `test` (`id`, `numDouble`, `numDoubleLimit`, `numDecimal`) VALUES ('1', '0', '0', '0.00000');

执行以下SQL三次:

# double列累加
UPDATE test SET numDouble = numDouble + 192.2 WHERE id = 1;
# double列取约数后累加,double类型会在0后的剩余长度补足一些小数,而大概就是这些小数导致的double类型求和后精度缺失
UPDATE test SET numDoubleLimit = ROUND(numDoubleLimit + 192.2, 5) WHERE id = 1;
# decimal列累加
UPDATE test SET numDecimal = numDecimal + 192.2 WHERE id = 1;

SELECT numDouble,numDoubleLimit,numDecimal FROM test;

发现结果如下:

mysql decimal sum mysql decimal sum丢失精度_double类型

 解决方案:

double列取约数后再累加求和,问题解决!

由此,总结如下:

1、追查日志,在大项目中由于定时器任务等复杂业务,会导致打印大量的日志,如果没有关键日志就会很难追踪bug;
2、开发完的项目在测试线总会出现一些奇奇怪怪的问题,最好可以直接连测试环境直接复现问题,然后再逐一排除问题;
3、找到问题的前后逻辑,并联系'上下文',有利于确认问题位置、确定问题处理方案;
4、设计表的小数字段类型时,形如金额、收货数量等要求精度的属性时可以用decimal;如果担心decimal类型的列取值问题,也可以使用double类型,但是最好不要用SQL直接累加double列,建议在外层代码计算好double列值,SQL语句仅set保存double列的值

*代码中的double列求和精度缺失,请参考:

double a = 192.2D;
double b = 0D;
b += a;
b += a;
b += a;
System.out.println(b);// 结果:576.5999999999999
        
BigDecimal aDecimal = new BigDecimal(Double.toString(192.2D));
BigDecimal bDecimal = new BigDecimal(Double.toString(0D));
bDecimal = bDecimal.add(aDecimal).add(aDecimal).add(aDecimal);
System.out.println(bDecimal.doubleValue());// 结果:576.6

 补充示例:

double a = 0.21;
double b = 0.0;
double c = 0.11;
        
double d = a - b - c;
System.out.println(d);// 0.09999999999999999
        
double e = 0.21 - 0 - 0.11;
System.out.println(e);// 0.09999999999999999