1. 程式人生 > 其它 >Mysql,version~沒變~,就是更新不成功,別慌!

Mysql,version~沒變~,就是更新不成功,別慌!

技術標籤:DBjavamysql

一個忙(mo)碌(yu)的下午,小航同學,突然大罵一聲,“TM ,見鬼了,version沒變,更新就是不成功”。

我看他,滿頭大汗,雙手握拳,面目猙獰,似乎又要發作,趕緊說:“不成功沒關係啊,重試就好,樂觀鎖一般是要重試的”

他略帶鄙視的說道:程式碼有重試了邏輯,我還加日誌了,結果發現version沒變,就是更新不成功。

作為對技術小有追求的人,他怎麼一說,立刻引起了我的好奇,隨後誠懇的說道,我能看看程式碼嗎?

小航,一句不發,雙手卻筆畫了個請的姿勢。

我仔細端詳,程式碼大致邏輯如下:

@Transactional(timeout = 36000, rollbackFor = Throwable.class)
public
void updateGoodNum(String id,Integer num) throws Exception { //1. select num as dbnum,version as dbversion from t where id=#{id} //2. update t set num=dbnum-num,version =dbversion +1 // where id=#{id} and version =dbversion ; // 如果更新失敗,重試1,2部總共3回 }

我輕嘆了口氣,在mysql連線工具執行了,如下語句,將截圖發給小航後,

擺出個大師的模樣說道:咱們測試環境隔離級別是RR(REPEATABLE-READ),在一個事務中重試是不行的!

小航難為情的說的:大哥,什麼是隔離級別啊? 為什麼不行啊? 怎麼改呢?

隔離級別

隔離級別說明
READ UNCOMMITTED未提交讀,會造成髒讀,違反永續性D
READ COMMITTED讀已提交資料, 會造成幻讀 違反一致性C
REPEATABLE READ(RR)

可重複讀,預設隔離級別,

事務中的select 語句會讀取事務開始前的快照,當然也能讀到本事務的更新內容

SERIALIZABLE不會使用mysql的mvcc機制,而是在每一個select請求下獲得讀鎖,在每一個update操作下嘗試獲得寫鎖

update操作是讀取當前值。

那在RR隔離級別下,為什麼在一個事務中重試是不行的呢?

表格模擬,為什麼不行?

開始事務前表t對應id=1的,version=1

事務A

begin

事務B

begin

1

select version from t where id=1;

-- 得到version=1

update t set version=2 where id=1;

commit;

2

update t set XX where id=1 and version=1;

// 更新失敗,update讀取當前,version=2

3

select version from t where id=1;

// 得到version=1

4commit

注意:事務中的select 是讀快照,update是讀當前

簡單說,就去其他事務,已經將version修改了2,事務A看到的還是事務開始前的值

也就是version為1.

解決方式

RR隔離級別下,將重試移到事務外. 即每次重試重新開一個事務

概要邏輯如下

/// 如果更新失敗,重試updateGoodNum總共3回
public AFacadeImpl{
    @Autowired
    AService aservice;
    public void updateGoodsNum(){
     boolean a  = aservice.updateGoodNum(id,num);
     if(!a){
         a  =  aservice.updateGoodNum(id,num);
     }
     if(!a){
         a  =  aservice.updateGoodNum(id,num);
     }
    }
}

public AServiceimpl implement AService{
@Transactional(timeout = 36000, rollbackFor = Throwable.class)
public boolean updateGoodNum(String id,Integer num)
      throws Exception {
    //1. select num as  dbnum,version as dbversion  from t where id=#{id}
    //2. update t set num=dbnum-num,version =dbversion +1  
    // where id=#{id} and version =dbversion  ;
    /
}
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==