資料同步與事務碰撞
1.事件起因
在專案開發過程中資料同步是經常遇見的事情,同步方法常見有:定時任務掃表同步、通過訊息佇列[MQ、Kafka等]同步,從資料的角度來劃分也有:全量資料同步、增量資料同步、變化資料同步,根據時間角度劃分也有:準實時同步,延時同步
在這次我們自己的專案開發中需要A庫增量及變化資料實時同步B庫,以供B應用能直接在B庫中查到,但是我們在程式碼編寫完成,交付給測試組人員測試過程中,測試發現A庫變化某條資料後版本號+1,比如版本號為3,計劃數目+1,比如結果為9, 但是B庫裡面的版本號同步過去是2,計劃數目為8,當A庫再次更新該條記錄,版本號累加到4,計劃數為10時,B庫同步到的資料是版本號為3,計劃數為9。 A庫於B庫是同步資料了,但是同步的資料永遠差一個版本。
2.事件分析
1.第一時間,我認為是Kafka機器下標計算錯誤,需要重啟Kafka對應的機器,讓其下標正確,後經過努力找到管理Kafka機器工作人員,幫忙重啟Kafka對應ip機器後,測試再次進行驗證,但依舊會A庫於B庫之間版本差一。
2.再次進行程式碼分析,發現更新資料的Service裡面,帶了一個事務,進行了更新後,再呼叫同步的Service裡面,先查了一遍A庫的資料,再同步給B庫的,我認為在一個事務裡,update的資料是還沒有更新到資料庫裡面去的,這時候再呼叫同步的Service去查A庫的資料,得到的就是舊的資料,然後同步給B庫,事務再進行提交,A庫更新為最新的資料,這樣就能夠解釋得通。
3.經過同事提醒,第二個推論是有問題的,一個事務內update資料後,再查其實就是最新的資料後,我們再次認真看了編寫的程式碼發現,再呼叫同步的Service由自己新起了一個事務,導致在這個事務裡面查到的資料就是舊的資料,這才是最重的原因,最後將呼叫同步的Service的@transactional註解給去掉後,測試再進行測試資料就正確了.
3.原理分析
可以看到第二次與第三次的分析,是有一個點的就是事務內update資料,再查是新的資料還是舊的資料的問題,這裡就需要理解事務的ACID特性裡面的I:隔離性
髒讀 | 不可重複讀 | 幻讀 | |
---|---|---|---|
Read Uncommitted | 可能 | 可能 | 可能 |
Read Committed | 不可能 | 可能 | 可能 |
Repeatable Read | 不可能 | 不可能 | 可能 |
Serilization | 不可能 | 不可能 | 不可能 |
- 髒讀:如果一個事務A對資料進行了更改,但是還沒有提交,而另一個事務B就可以讀到事務A尚未提交的更新結果。這樣,當事務A進行回滾時,那麼事務B始讀到的資料就是一筆髒資料。
1:小明工資1000,財務人員將小明工資改成8000,但是未提交事務。
2:小明發現自己工資8000(事務未提交期間)。
3:財務發現操作錯誤,回滾事務。工資變成1000。 描述:在事務未提交的時候,另一個事務讀取到了未提交事務的資料。
- 不可重複讀:同一個事務在事務過程中,對同一個資料進行讀取操作,讀取到的結果不同。例如,事務B在事務A的更新操作前讀到的資料,跟在事務A提交此更新操作後讀到的資料,可能不同。
要避免不可重複讀,需要將事務所操作的記錄都加上鎖,不允許其他事務對此記錄進行寫操作。
1:事務1中,小明發現自己工資1000,操作沒有完成。
2:事務2中,財務修改小明的工資為8000,提交事務。
3:事務1中,小明再次讀取自己工資發現變成了8000。
描述:針對與修改資料,一次事務讀取資料前後兩次不一樣。(願意期間被別的事務修改了值)
- 幻讀:同樣一個查詢在整個事務中多次執行,查詢所得的結果不同。例如,事務A對全部記錄做了更新操作,尚未提交前,事務B又插入了一條記錄,那麼事務A再次讀取資料庫時,會發現還有一條記錄(即事務B新插入的記錄)沒有做更新。
1:事務1中,讀取工資為1000的有1000個員工。
2:事務2向員工表新增一條員工資料,工資也是1000.
3:事務1中,再次讀取發現工資為1000的員工有1001個。
描述:針對新增或者刪除,一次事務讀取資料前後兩次不一樣。(願意期間被別的事務新增或者刪除了值)
Mysql-InnoDb 預設的隔離級別是Repeatable Read
Oracle 預設的隔離級別是Read Committed