關於mysql事務更新可讀性問題

最近在工作中遇到了一個事務之間可讀性的問題,業務場景是這樣的:

    用戶創建了訂單之後會在後臺創建一個延時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讀到相同的結果集

到目前算是解決的爲什麼訂單狀態異常的問題,果然上線之後用戶類似的反饋沒了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章