最近組織團隊內(nèi)技術(shù)培訓(xùn),劉聰為分享的一個跟事務(wù)和寫數(shù)據(jù)庫相關(guān)的case(bug)很有代表性。用事務(wù),要小心!
從策劃到設(shè)計制作,每一步都追求做到細膩,制作可持續(xù)發(fā)展的企業(yè)網(wǎng)站。為客戶提供成都網(wǎng)站設(shè)計、成都網(wǎng)站制作、網(wǎng)站策劃、網(wǎng)頁設(shè)計、域名注冊、雅安服務(wù)器托管、網(wǎng)絡(luò)營銷、VI設(shè)計、 網(wǎng)站改版、漏洞修補等服務(wù)。為客戶提供更好的一站式互聯(lián)網(wǎng)解決方案,以客戶的口碑塑造優(yōu)易品牌,攜手廣大客戶,共同發(fā)展進步。一、故障現(xiàn)象
車輛交付履約流程上兩個節(jié)點(工程項目)A和B, A修改一條數(shù)據(jù)記錄item(工單),然后發(fā)消息給B,B也會對item進行修改。
故障現(xiàn)象,有時候(不是必現(xiàn))感覺A沒有成功修改item這條數(shù)據(jù),而日志顯示A修改成功了數(shù)據(jù)item!
看一下具體代碼實現(xiàn)。下圖是工程A代碼,3個紅框依次動作。
1、開啟事務(wù)
2、修改工單記錄item
3、向下游節(jié)點發(fā)送mq消息
下圖是下游消費mq消息的節(jié)點B,紅框表示采用JPA技術(shù)修改數(shù)據(jù)記錄item
二、原因分析
這個過程總共經(jīng)歷5個步驟,見下圖
1、節(jié)點A開啟一個事務(wù),修改數(shù)據(jù)表中某條數(shù)據(jù)item
2、A向B發(fā)送mq消息,再做些其他事情,提交事務(wù)
3、節(jié)點B,消費mq消息
4、節(jié)點B讀出數(shù)據(jù)item
5、節(jié)點B在內(nèi)存中修改數(shù)據(jù)item某些字段,寫回數(shù)據(jù)庫
注意到第1、2步驟是在一個事務(wù)中。存在一種可能,B節(jié)點收到mq消息,執(zhí)行第4步驟,讀取item數(shù)據(jù)后,步驟1、2的事務(wù)才完成提交。由于數(shù)據(jù)庫事務(wù)隔離級別,這種情況下,第4步驟讀到的數(shù)據(jù)并不是A節(jié)點在第1步寫的,已經(jīng)讀到臟數(shù)據(jù)了。當?shù)?步寫回數(shù)據(jù)的時候,就可能造成老數(shù)據(jù)覆蓋A寫的新數(shù)據(jù)。
這里有兩個細分場景
1、第1步、第5步修改同一個字段。這種情況,第4步驟讀到臟數(shù)據(jù)
2、第1步、第5步修改不同字段。第4步讀到col2字段的oldvalue,第5步目的是修改col3的值,但是采用jpa或者mybatis的一些默認寫法,會把col2的oldvalue更新回數(shù)據(jù)庫。
一般的ORMapping框架利用一個vo對象寫數(shù)據(jù)庫記錄,沒有修改的字段不會更新(代碼里并沒有改col2的值),但是第4步讀取數(shù)據(jù)后,第1步對數(shù)據(jù)item進行了修改。這樣默認的寫庫方法,會check記錄的變化,然后把col2字段的值更新。這樣就出現(xiàn)了舊值覆蓋新值的問題。
三、解決辦法
1、考慮到實施成本,如果修改不同的字段,不存在競爭關(guān)系。只需要在第5步寫庫的環(huán)節(jié)指定更新字段就能快速解決這個問題。事實上,生產(chǎn)環(huán)境下也是選擇的這個方案臨時修復(fù)。
2、解決辦法1顯然不夠優(yōu)秀。更好的做法,把第2步發(fā)mq消息從事務(wù)中拆出來,等第1步操作commit后在發(fā)mq消息。這個辦法涉及到一些邏輯的梳理(業(yè)務(wù)代碼里會有不少的if……else),代碼的改動。這樣處理仍然不夠完美,第1步執(zhí)行完了,第2步失敗了怎么辦?在這里可能需要一些額外的代碼工作保證第2步執(zhí)行成功。
3、如果業(yè)務(wù)壓力不大,也可以考慮從數(shù)據(jù)庫的事務(wù)隔離級別方面入手來解決這個問題。
4、業(yè)務(wù)上,第1步到第5步如果需要強一致,了解一下分布式事務(wù)
https://www.jianshu.com/p/16b1baf015e8