当前位置:资讯

当 SQL Server(mssql-jdbc) 遇上 BigDecimal → 精度丢失,真坑!

2023-04-28 09:34:51 来源:博客园
开心一刻

中午和哥们一起喝茶


【资料图】

哥们说道:晚上喝酒去啊

我:不去,我女朋友过生日

哥们瞪大眼睛看着我:你有病吧,充气的过什么生日

我生气到:有特么生产日期的好吧

需求背景

系统对接了外部系统,调用外部系统的接口需要付费,一个接口一次调用付费 0.03 元

同一个月内,同一个接口最高付费 25 元

统计每个月的付费情况

需求清楚了不?不清楚? 给大家举个案例

这下明白了吧

明白了需求,相信大家都会觉得很简单,不就是一个分组汇总吗?

客官说的对,但生活总会给我们一点surprise

我们慢慢往下看

环境准备

SQL Server版本:SQL Server 2017

MySQL版本:8.0.27

引入MySQL,是为了跟SQL Server做对比

SQL Server建表并初始化数据

CREATE TABLE tbl_interface_call_times (    id BIGINT PRIMARY KEY IDENTITY(1,1),    call_month INT NOT NULL,        interface varchar(50) NOT NULL ,        times INT NOT NULL);INSERT INTO tbl_interface_call_times(call_month, interface, times) VALUES(202301, "interface1", 800),(202301, "interface2", 1000),(202301, "interface3", 100),(202302, "interface1", 833),(202302, "interface2", 834),(202302, "interface3", 134),(202302, "interface4", 243),(202302, "interface5", 2143);
View Code

MySQL建表并初始化数据

CREATE TABLE tbl_interface_call_times (    id INT UNSIGNED NOT NULL AUTO_INCREMENT,    call_month INT NOT NULL COMMENT "月份",        interface varchar(50) NOT NULL COMMENT "接口",        times INT NOT NULL COMMENT "调用次数",    PRIMARY KEY(id)) COMMENT "接口调用次数";INSERT INTO tbl_interface_call_times(call_month, interface, times) VALUES(202301, "interface1", 800),(202301, "interface2", 1000),(202301, "interface3", 100),(202302, "interface1", 833),(202302, "interface2", 834),(202302, "interface3", 134),(202302, "interface4", 243),(202302, "interface5", 2143);
View Code

汇总每个月的付费,SQL该如何写?

很简单的啦,如下所示

SELECT call_month,     SUM(        CASE WHEN times * 0.03 > 25 THEN 25        ELSE times * 0.03        END    ) monthFeeFROM tbl_interface_call_timesGROUP BY call_month
View Code

通用写法,SQL Server和MySQL都支持

我们看下查询结果

一切都很正常,觉得世界真美好!

问题复现

我们不能光玩数据库吧?

不得像这样雨露均沾?

必须把spring-boot、MyBatis-Plus安排上

mysql-jdbc版本:8.0.21,mssql-jdbc版本:6.2.1.jre8

完整代码:mybatis-plus-dynamic-datasource

访问:http://localhost:8081/interface/summary?startMonth=202301&endMonth=202302

你会发现,你心心念念的surprise终于出现了!

正确应该是 86.3.3哪去了?

直查数据库是没问题的呀

莫非MyBatis-Plus有问题?

我们切到MySQL试试;将InterfaceCallTimesServiceImpl上的数据源改成mysql_db

然后重启,我们再访问:http://localhost:8081/interface/summary?startMonth=202301&endMonth=202302

这说明应该不是MyBatis的问题,那不完犊子了?

问题解决

是不是束手无策了? 也不是,我们可以Bing一下的嘛

你会发现说的都是批量insert的时候,BigDecimal有精度丢失

单条插入的时候,是没有精度丢失的

然后了,大家试出了一条件论:批量插入数据时,如果插入的数据精度不统一,最终入库的数据精度统一按最低的精度入库

虽说我们只是查询,莫非也需要精度统一?

精度统一

试试呗,反正又不要钱

重启,神奇的事情发生了

.3它回来了! 相信此刻的你肯定有一种与知己久别重逢的激动

问题貌似解决了,但说实话,这种处理方式你用的放心吗?

升级 mssql-jdbc 版本

我们好好捋一下,程序从SQL Server获取数据,经历了哪些环节?

只有三个:MyBatis-Plus ->mssql-jdbc->SQL Server

前面我们已经排除了SQL Server和MyBatis-Plus

那问题肯定就出在mssql-jdbc身上了

问题又来了,该如何从mssql-jdbc上找问题了?

开源的东西从它的官方找相关的issue,肯定不止我们遇到这样的问题,那么肯定有人会给官方提了issue

issue地址:https://github.com/microsoft/mssql-jdbc/issues

直接搜索BigDecimal,像这样

回车之后,你会发现,原来你不是一个人在战斗

那就去里面找呗,发现 #1489 跟我们的问题有点像,仔细去读,发现关联了 #1912

读到 1912 的末尾,你会发现又关联了 #2051,我们去看看 2051

那就是在这里修复了呀,那它关联的版本是哪个了?

然后我们在回到我们搜索BigDecimal相关issue的时候,你会发现

12.2.0已经发布了

如果觉得看英文的费劲,那就看中文的:Microsoft JDBC Driver for SQL Server 发行说明

这总看得懂了吧

那就将mssql-jdbc升级到12.2.0试试

入参不用统一精度,结果也正确了!

但是,又开始转折了,你以为12.2.0就高枕无忧了?

BigDecimal的问题都延续到12.3.0了

此刻大家的心情是怎样的,请评论区留言

总结

1、当mssql-jdbc遇上BigDecimal,两种处理方式

1.1 BigDecimal类型的入参全部统一成最高精度

1.2 版本升级到12.2.0,但还是有问题,需要考虑业务是否会触发12.2.0的bug

2、 mssql-jdbc的BigDecimal的问题从2016年就开始出现了,到了现在(2023)还存在问题,我真的想对官方说一句

关键词:


玛斯柯采用艾迈斯欧司朗LED解决方案

2023-04-27

资讯来源:艾迈斯欧司朗

同心聚首话友情 共谋大业启新程 要闻

2023-04-27

资讯来源:大江网-信息日报

教育