MySQL 技巧 如何實現樂觀鎖?

2021-10-22 16:35:05 字數 3197 閱讀 5868

使用 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()

@service

@transaction

pubic class goodssaleservice

int count = goodssale.getcount() + count;

goodssale.setcount(count);

int count = dao.updatecount(goodssale);

if (count == 0)

}}

使用的 dao 為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)

版本號

update

goods_sale

set count = #, data_version = data_version + 1

where goods_sale_id = #

and data_version = #

dao 調整之後,事務 a 和事務 b 的變化如下:

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....