最近在工作中遇到了一個事務之間可讀性的問題,業務場景是這樣的:
用戶創建了訂單之後會在後臺創建一個延時15分鐘的任務,當15分鐘到的時候會檢測這個訂單是否支付了,如果支付了那麼就會取消這個延時任務,如果沒有支付那麼就取消這個訂單,但是現在有用戶反饋已經支付了但是訂單還是取消了,於是就這個問題我到代碼裏找了一下,發現了這個可讀性的問題。代碼邏輯是這樣的
@Transactional(value = "gaotuTransactionManager", readOnly = false, rollbackFor = RuntimeException.class)
public int cancelOrder(Long payNumber) {
//1、獲取訂單信息
//2、檢查是否是未支付
//3、更新payInfo
return 1;
}
在這裏更新payInfo使用的sql是
update order set status = 1 where payNumber = 123123
這樣這裏就會出現一個問題,就是當上面1、2步走完的時候恰好用戶支付了訂單,但是因爲在2檢查的時候是未支付的所以3還是會把這個訂單取消掉,所以這裏的sql這樣寫是有問題的,改成下面這樣就可以了。
update order set status = 1 where payNumber = 123123 and status = 2
但是這樣是不是真的就沒有問題了呢,於是我到測試環境去驗證了一下,結果如下:
首先我開了兩個mysql鏈接並開啓事務分別爲A和B,A先開啓事務但是B先結束事務:
A1: start transaction;
B1: start transaction;
A2: select * from order where payNumber = 1;
B2: update order set status = 1 where payNumber = 1;
A3: select * from order where payNumber = 1;
B3: commit;
A4: select * from order where payNumber = 1;
這個時候我發現三次select請求出來的結果重的status都是2也就是說盡管B3已經更新了但是A4還是讀取的原來的數據,原因是mysql的innodb 默認的隔離級別是RR也就是可重複讀,可重複讀的情況下,某個事務首次read記錄的時間爲T,未來不會讀取到T時間之後已提交事務寫入的記錄,以保證連續相同的read讀到相同的結果集
如果這個時候把隔離級別換成是RC也就是讀提交呢?結果會是什麼樣?
結果就是A1:2,A2:2,A3:2,A4:1,在A4的時候讀取到了事務B提交的新的數據。
關於這個RR和RC爲啥出現這種情況是因爲mysql的innodb使用了快照讀這個功能,什麼是快照讀呢?
MySQL數據庫,InnoDB存儲引擎,爲了提高併發,使用MVCC機制,在併發事務時,通過讀取數據行的歷史數據版本,不加鎖,來提高併發的一種不加鎖一致性讀(Consistent Nonlocking Read)。
在讀提交(RC),可重複讀(RR)兩個不同的事務的隔離級別下,快照讀有什麼不同呢?
-
RC下,快照讀總是能讀到最新的行數據快照,當然,必須是已提交事務寫入的
-
RR下,某個事務首次read記錄的時間爲T,未來不會讀取到T時間之後已提交事務寫入的記錄,以保證連續相同的read讀到相同的結果集
到目前算是解決的爲什麼訂單狀態異常的問題,果然上線之後用戶類似的反饋沒了。