事務註解(@Transactional)引起的資料覆蓋故障

普通程式設計師發表於2019-06-25

最近組織團隊內技術培訓,劉聰為分享的一個跟事務和寫資料庫相關的case(bug)很有代表性。用事務,要小心!

一、故障現象

車輛交付履約流程上兩個節點(工程專案)A和B, A修改一條資料記錄item(工單),然後發訊息給B,B也會對item進行修改。

故障現象,有時候(不是必現)感覺A沒有成功修改item這條資料,而日誌顯示A修改成功了資料item!

看一下具體程式碼實現。下圖是工程A程式碼,3個紅框依次動作。

1、開啟事務

2、修改工單記錄item

3、向下遊節點傳送mq訊息 

事務註解(@Transactional)引起的資料覆蓋故障

下圖是下游消費mq訊息的節點B,紅框表示採用JPA技術修改資料記錄item

事務註解(@Transactional)引起的資料覆蓋故障

二、原因分析

這個過程總共經歷5個步驟,見下圖 

事務註解(@Transactional)引起的資料覆蓋故障

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步驟讀到髒資料 

事務註解(@Transactional)引起的資料覆蓋故障

2、第1步、第5步修改不同欄位。第4步讀到col2欄位的oldvalue,第5步目的是修改col3的值,但是採用jpa或者mybatis的一些預設寫法,會把col2的oldvalue更新回資料庫。

一般的ORMapping框架利用一個vo物件寫資料庫記錄,沒有修改的欄位不會更新(程式碼裡並沒有改col2的值),但是第4步讀取資料後,第1步對資料item進行了修改。這樣預設的寫庫方法,會check記錄的變化,然後把col2欄位的值更新。這樣就出現了舊值覆蓋新值的問題。 

事務註解(@Transactional)引起的資料覆蓋故障

三、解決辦法

1、考慮到實施成本,如果修改不同的欄位,不存在競爭關係。只需要在第5步寫庫的環節指定更新欄位就能快速解決這個問題。事實上,生產環境下也是選擇的這個方案臨時修復。

2、解決辦法1顯然不夠優秀。更好的做法,把第2步發mq訊息從事務中拆出來,等第1步操作commit後在發mq訊息。這個辦法涉及到一些邏輯的梳理(業務程式碼裡會有不少的if……else),程式碼的改動。這樣處理仍然不夠完美,第1步執行完了,第2步失敗了怎麼辦?在這裡可能需要一些額外的程式碼工作保證第2步執行成功。

3、如果業務壓力不大,也可以考慮從資料庫的事務隔離級別方面入手來解決這個問題。

4、業務上,第1步到第5步如果需要強一致,瞭解一下分散式事務

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556438/viewspace-2648709/,如需轉載,請註明出處,否則將追究法律責任。

相關文章