Mysql,version~沒變~,就是更新不成功,別慌!
一個忙(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 | |
4 | commit |
注意:事務中的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 ;
/
}
}