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

青石路發表於2023-04-28

開心一刻

  中午和哥們一起喝茶

  哥們說道:晚上喝酒去啊

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

  哥們瞪大眼睛看著我:你有病吧,充氣的過什麼生日

  我生氣到:有特麼生產日期的好吧

需求背景

  系統對接了外部系統,呼叫外部系統的介面需要付費,一個介面一次呼叫付費 0.03 元

  同一個月內,同一個介面最高付費 25 元

  統計每個月的付費情況

  需求清楚了不?不清楚? 給大家舉個案例

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

  這下明白了吧

  明白了需求,相信大家都會覺得很簡單,不就是一個分組彙總嗎?

  客官說的對,但生活總會給我們一點 surprise 

  我們慢慢往下看

環境準備

   SQL Server 版本: SQL Server 2017 

   MySQL 版本: 8.0.27 

  引入 MySQL ,是為了跟 SQL Server 做對比

   SQL Server 建表並初始化資料

當 SQL Server(mssql-jdbc) 遇上 BigDecimal → 精度丟失,真坑!
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 建表並初始化資料

當 SQL Server(mssql-jdbc) 遇上 BigDecimal → 精度丟失,真坑!
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 該如何寫?

  很簡單的啦,如下所示

當 SQL Server(mssql-jdbc) 遇上 BigDecimal → 精度丟失,真坑!
SELECT call_month, 
    SUM(
        CASE WHEN times * 0.03 > 25 THEN 25
        ELSE times * 0.03
        END
    ) monthFee
FROM tbl_interface_call_times
GROUP BY call_month
View Code

  通用寫法, SQL Server 和 MySQL 都支援

  我們看下查詢結果

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

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

問題復現

  我們不能光玩資料庫吧?

  不得像這樣雨露均霑?

  必須把 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 

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

  然後重啟,我們再訪問: http://localhost:8081/interface/summary?startMonth=202301&endMonth=202302 

  這說明應該不是 MyBatis 的問題,那不完犢子了?

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

問題解決

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

  你會發現說的都是批次 insert 的時候, BigDecimal 有精度丟失

  單條插入的時候,是沒有精度丟失的

  然後了,大家試出了一條件論: 批次插入資料時,如果插入的資料精度不統一,最終入庫的資料精度統一按最低的精度入庫 

  雖說我們只是查詢,莫非也需要 精度統一 ?

  精度統一

  試試唄,反正又不要錢

當 SQL Server(mssql-jdbc) 遇上 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 ,像這樣

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

  回車之後,你會發現,原來你不是一個人在戰鬥

  那就去裡面找唄,發現 #1489 跟我們的問題有點像,仔細去讀,發現關聯了 #1912

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

  讀到 1912 的末尾,你會發現又關聯了 #2051,我們去看看 2051

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

  那就是在這裡修復了呀,那它關聯的版本是哪個了?

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

  然後我們在回到我們搜尋 BigDecimal 相關 issue 的時候,你會發現

   12.2.0 已經發布了

  如果覺得看英文的費勁,那就看中文的:Microsoft JDBC Driver for SQL Server 發行說明

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

  這總看得懂了吧

  那就將 mssql-jdbc 升級到 12.2.0 試試

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

  入參不用統一精度,結果也正確了!

  但是,又開始轉折了,你以為 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 )還存在問題,我真的想對官方說一句

相關文章