您好,登錄后才能下訂單哦!
最近組織團隊內技術培訓,劉聰為分享的一個跟事務和寫數據庫相關的case(bug)很有代表性。用事務,要小心!
一、故障現象
車輛交付履約流程上兩個節點(工程項目)A和B, A修改一條數據記錄item(工單),然后發消息給B,B也會對item進行修改。
故障現象,有時候(不是必現)感覺A沒有成功修改item這條數據,而日志顯示A修改成功了數據item!
看一下具體代碼實現。下圖是工程A代碼,3個紅框依次動作。
1、開啟事務
2、修改工單記錄item
3、向下游節點發送mq消息
下圖是下游消費mq消息的節點B,紅框表示采用JPA技術修改數據記錄item
二、原因分析
這個過程總共經歷5個步驟,見下圖
1、節點A開啟一個事務,修改數據表中某條數據item
2、A向B發送mq消息,再做些其他事情,提交事務
3、節點B,消費mq消息
4、節點B讀出數據item
5、節點B在內存中修改數據item某些字段,寫回數據庫
注意到第1、2步驟是在一個事務中。存在一種可能,B節點收到mq消息,執行第4步驟,讀取item數據后,步驟1、2的事務才完成提交。由于數據庫事務隔離級別,這種情況下,第4步驟讀到的數據并不是A節點在第1步寫的,已經讀到臟數據了。當第5步寫回數據的時候,就可能造成老數據覆蓋A寫的新數據。
這里有兩個細分場景
1、第1步、第5步修改同一個字段。這種情況,第4步驟讀到臟數據
2、第1步、第5步修改不同字段。第4步讀到col2字段的oldvalue,第5步目的是修改col3的值,但是采用jpa或者mybatis的一些默認寫法,會把col2的oldvalue更新回數據庫。
一般的ORMapping框架利用一個vo對象寫數據庫記錄,沒有修改的字段不會更新(代碼里并沒有改col2的值),但是第4步讀取數據后,第1步對數據item進行了修改。這樣默認的寫庫方法,會check記錄的變化,然后把col2字段的值更新。這樣就出現了舊值覆蓋新值的問題。
三、解決辦法
1、考慮到實施成本,如果修改不同的字段,不存在競爭關系。只需要在第5步寫庫的環節指定更新字段就能快速解決這個問題。事實上,生產環境下也是選擇的這個方案臨時修復。
2、解決辦法1顯然不夠優秀。更好的做法,把第2步發mq消息從事務中拆出來,等第1步操作commit后在發mq消息。這個辦法涉及到一些邏輯的梳理(業務代碼里會有不少的if……else),代碼的改動。這樣處理仍然不夠完美,第1步執行完了,第2步失敗了怎么辦?在這里可能需要一些額外的代碼工作保證第2步執行成功。
3、如果業務壓力不大,也可以考慮從數據庫的事務隔離級別方面入手來解決這個問題。
4、業務上,第1步到第5步如果需要強一致,了解一下分布式事務
https://www.jianshu.com/p/16b1baf015e8
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。