使用 mysql 5.7 做測試,資料庫引擎為 innodb,資料庫隔離級別為可重複讀(repeatable-read),讀讀共享,讀寫互斥。在這個隔離級別下,在多事務併發的情況下,還是會出現資料更新的衝突問題。
先分析一下更新衝突的問題是如何產生的。
假設我們有一張銷量表goods_sale
,表結構如下:
字段資料型別
說明goods_sale_id
varchar(32)
銷量 id
goods_id
varchar(32)
商品 id
count
int(11)
銷量比如在某一時刻事務 a 和事務 b,在同時操作表goods_sale
的 goods_id = 20191017344713049535651840506935 的資料,當前銷量為 100。
goods_sale_id
goods_id
count
20191017344778600995856384326638
20191017344713049535651840506935
100兩個事務的內容一樣,都是先讀取的資料,count +100 後更新。
我們這裡只討論樂觀鎖的實現,為了便於描述,假設專案已經整合 spring 框架,使用 mybatis 做 orm,service 類的所有方法都使用了事務,事務傳播級別使用propagation_required
,在事務失敗會自動回滾。
service 為goodssaleservice
,更新數量的方法為addcount()
。
使用的 dao 為@service
@transaction
pubic class goodssaleservice
int count = goodssale.getcount() + count;
goodssale.setcount(count);
int count = dao.updatecount(goodssale);
if (count == 0)
}}
goodssaledao
,有兩個方法
public inte***ce goodssaledao
好了,假設現在有兩個執行緒同時呼叫了
select
from goods_sale
where goods_id = #
update
goods_sale
set count = #,
where goods_sale_id = #
goodssaleservice#addcount
,操作同一行資料,會有什麼問題?
假設這兩個執行緒對應的事務分為事務 a 和事務 b。用一張流程圖來說明問題:
mysql-多事務更新衝突
更新衝突了!兩次addcount(100)
,結果應該是 300,結果還是 200。
該如何處理這個問題,有乙個簡單粗暴的方法,既然這裡多執行緒訪問會有執行緒安全問題,那就上鎖,方法加入synchronized
進行互斥。
public synchronized void addcount(string goodsid, integer count)
這個方案確實也可以解決問題,但是這種簡單互斥的做法,鎖的粒度太高,事務排隊執行,併發度低,效能低。但如果是分布式應用,還得考慮應用分布式鎖,效能就更低了。
考慮到這些更新衝突發生的概率其實並不高。這裡討論另一種解決方案,使用樂觀鎖來實現。原理就是基於cas
,比較並交換資料,如果發現被更新過了,直接更新失敗。然後加入自旋(自迴圈)接著更新,直到成功。樂觀就在於我們相信衝突發生概率低,如果發生了,就用一種廉價的機制迅速發現,快速失敗。
我們來討論如何實現它。資料庫表goodssale
新增一行data_version
來記錄資料更新的版本號。新的表結構如下:
字段資料型別
說明goods_sale_id
varchar(32)
銷量 id
goods_id
varchar(32)
商品 id
count
int(11)
銷量data_version
int(11)
版本號
dao 調整之後,事務 a 和事務 b 的變化如下:
update
goods_sale
set count = #, data_version = data_version + 1
where goods_sale_id = #
and data_version = #
mysql-多事務更新衝突-加鎖
有了發現衝突快速失敗的方案,要想讓更新成功,可以在goodssaleservice
中加入自旋,重新開始事務業務邏輯的執行,直到沒有發生衝突,更新成功。自旋的實現有兩種,一種是使用迴圈,一種是使用遞迴。
迴圈實現:
遞迴實現:public void addcount(string goodsid, integer count)
int count = goodssale.getcount() + count;
goodssale.setcount(count);
int count = dao.updatecount(goodssale);
if (count > 0)
}}
通過樂觀鎖+自旋的方式,解決資料更新的執行緒安全問題,而且鎖粒度比互斥鎖低,併發效能好。public void addcount(string goodsid, integer count)
int count = goodssale.getcount() + count;
goodssale.setcount(count);
int count = dao.updatecount(goodssale);
if (count == 0)
}
mysql樂觀鎖實現 mysql樂觀鎖實現
在多使用者環境中,在同一時間可能會有多個使用者更新相同的記錄,這會產生衝突。這就是著名的併發性問題。典型的衝突有 1.丟失更新 乙個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如 使用者a把值從6改為2,使用者b把值從2改為6,則使用者a丟失了他的更新。2.髒讀 當乙個事務讀取其它完成...
mysql 樂觀鎖實現 mysql 樂觀鎖實現
一 為什麼需要鎖 併發控制 在多使用者環境中,在同一時間可能會有多個使用者更新相同的記錄,這會產生衝突。這就是著名的併發性問題。典型的衝突有 1.丟失更新 乙個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如 使用者a把值從6改為2,使用者b把值從2改為6,則使用者a丟失了他的更新。2....
mysql 樂觀鎖實現
一 為什麼需要鎖 併發控制 在多使用者環境中,在同一時間可能會有多個使用者更新相同的記錄,這會產生衝突。這就是著名的併發性問題。典型的衝突有 1.丟失更新 乙個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如 使用者a把值從6改為2,使用者b把值從2改為6,則使用者a丟失了他的更新。2....