讓你徹底搞明白為什麼用樂觀鎖

2021-10-01 05:01:53 字數 3564 閱讀 2536

看了掘金小冊以後,自認為對mysql的鎖有所認知,但是反而看的越多越困惑, 在mysql 的innodb 引擎下, 預設的隔離級別下, mysql已經通過讀使用mvcc,寫使用加鎖 的方式解決了併發的四大資料庫問題 髒寫, 髒讀, 不可重複讀, 幻讀. 所以看到這裡的時候我就在想,既然mysql底層機制已經解決了併發問題,那麼為什麼還有好多人說自己通過版本號機制 來實現樂觀鎖避免併發問題呢? 

這裡的併發問題到底是什麼問題? 如果是髒寫這種問題,mysql不是已經解決了嗎? 多此一舉用版本號的意義是什麼呢?

首選要理清為什麼要用樂觀鎖,我們要看看悲觀鎖和樂觀鎖到底是什麼

樂觀鎖

這其實是一種思想,當執行緒去拿資料的時候,認為別的執行緒不會修改資料,就不上鎖,但是在更新資料的時候會去判斷以下其他執行緒是否修改了資料。通過版本來判斷,如果資料被修改了就拒絕更新,之所以叫樂觀鎖是因為並沒有加鎖。

悲觀鎖

當執行緒去拿資料的時候,總以為別的執行緒會去修改資料,所以它每次拿資料的時候都會上鎖,別的執行緒去拿資料的時候就會阻塞。

1.首先,樂觀鎖和悲觀鎖是一種理念,也就是說,它不是你認為的"鎖", 具體來說,樂觀和悲觀鎖是一種 "鎖的實現機制".

2. 在mysql裡,沒有樂觀鎖,只有悲觀鎖,mysql裡的比如說共享鎖,獨佔鎖等等, 都是具有真實的資料記憶體結構,它們是真正的"鎖"

在舉例子之前,我們要先了解,預設情況下,對於select操作, mysql是不加鎖的 ,對於寫操作 update 是會加鎖的,select 不加鎖如何解決幻讀 ,髒讀這些問題 暫時不屬於我們現在討論範疇. insert delete  也不屬於我們討論的範疇.

舉個栗子:

假如現在你接手乙個需求, 為使用者賬戶充值金額(充值10元), 比如你寫了兩個sql,並且你很有心的加上了手動事務

1. begin

2.  select 該使用者的 餘額,   得到 100元

3.  update 該使用者的 餘額 ,   具體操作是 set money = 100 + 10 , 

4. commit , 成功更新餘額為 110 元

很簡單的需求,看似沒什麼困難,但是,如果有兩個人恰好同時給這乙個賬戶進行充值,會發生什麼問題呢? 

你可能覺得也沒什麼問題, 因為你覺得你機智的加上了事務, 你認為即使兩個請求同事過來, 事務也會挨個排隊執行, a事務執行完,b事務才會執行, 真的是這樣嗎?

顯然不是, 由於mysql並沒有使用序列化 這個隔離級別, 由於 讀 並沒有加鎖  ,所以真實執**況可能是下面這樣的.

發生時間

session a

session b1.

begin

2.select money  =  100

(查到此時賬戶餘額是100)

3.begin

4.select money  =  100

(查到此時賬戶餘額是100)

5.update money = 100 + 10

(更新金額為100+ 10)

6.commit

7.update money = 100 + 10

(更新金額為100+ 10)

8.commit

9.由此可見,預設情況下,對於某些特定的業務需求, 必須要進行額外的處理,不然顯然會發生大問題. 

所以, 對於這種情況下的解決方案, 網上有兩種方案,使用 樂觀鎖或者悲觀鎖 ,(再次強調,樂觀鎖悲觀鎖是一種思想)

悲觀鎖,就是某個事物運算元據的時候害怕別的事務也操作,所以加上"鎖" 給鎖住,這樣別的事務將會被阻塞,

在mysql中, 執行select的時候 如果 加上 for update ,那麼這條語句即使是select ,也會加上獨佔鎖(獨佔鎖就是不管別的操作是什麼,只要是需要獲取鎖的操作,必須等待我把自己的鎖給釋放了,別人才能執行)

發生時間

session a

session b1.

begin

2.select money  =  100for update

(查到此時賬戶餘額是100,加上了獨佔鎖,b事務無法插入,必須等待a事務提交後把鎖釋放)

3.update money = 100 + 10

(更新金額為100+ 10)

4.commit

(這條記錄的獨佔鎖被釋放了,此時b事務才可以操作)

5.begin

6.select money  =  110for update

(查到此時賬戶餘額是110,由於b事務select的時候也加了for update ,那麼 意味著它也需要獲取這個記錄的鎖才能讀,顯然由於a事務先拿到了鎖,所以b事務必須等a commit)

7.update money = 110 + 10

(更新金額為110+ 10)

更新成功為 120 元

8.commit

9.樂觀鎖常見的機制是使用version,每個事務都會先查一下版本號,更新的時候時候同時再比對庫里的版本號與剛才查的版本號是否一直,如果不一致,說明有其他事務更新了這個記錄, 說明自己當前操作的資料不是最新的, 所以會嘗試.

如果我們的一些業務場景不允許讀取記錄的舊版本,而是每次都必須去讀取記錄的最新版本,這種業務場景下,select 加 獨佔鎖和使用version實現樂觀鎖 都是可選的. 

發生時間

session a

session b1.

begin

2.select money =  100 ,version = 1

(查到此時賬戶餘額是100,版本號是1)

3.begin

4.select money =  100 ,version = 1

(查到此時賬戶餘額是100,版本號是1)

5.update money = 100 + 10

(update同時會比對當前庫里的version是否等於 1,顯然是等於的,所以更新金額為100+ 10)

6.commit 

(這個時候資料庫的這個記錄的version已經為2了)

7.update money = 100 + 10 ,

(update會比對version,由於此時資料庫version=2,但是自己剛才查到的version是1 , 所以無法跟新

8.rollback

這裡由於更新失敗,會再次嘗試

9.begin

10.select money =  110 ,version = 2

(查到此時賬戶餘額是110,版本號是2)

11.update money = 110 + 10 ,

(update會比對version,此時資料庫version=2,自己剛才查到的version也是2 , 所以可以更新

12.commit

充值成功了

使用樂觀鎖的場景大家常說是併發情況下,這裡的併發出現的問題,是業務邏輯上的問題,並不是資料庫本身在併發情況下發生的髒讀, 不可重複讀 等問題. 請一定要悉知.

Spring 是幹什麼用的?讓你明白

1沒有spring的時候 沒有服務員的時候 顧客 選單 自己拿選單,依賴選單點菜 2有spring的時候 服務員在 顧客 服務員 選單 服務員主動把選單拿給你點菜 3顧客和選單就是兩個bean 服務員是ioc容器 4demo userservice userservice userservice c...

python為什麼用flask 為什麼用flask

flask是python在web開發領域乙個輕量級的框架,為什麼選擇flask呢?此文可能會給你答案。選擇flask的原因 1.微框架 簡潔 只做它需要做的,給開發展提供了很大的擴充套件性。2.flask和相關的依賴 jinja2 werkzeug 設計得非常優秀,用著簡單。3.開發效率非常高,比如...

為什麼用css sprites

在分析各個 的css時,我們經常可以看到一些 有很多的元素共享了一張背景,而這張背景包含了所有這些元素需要的背景,這種技術就叫做css sprites。例如 的css sprites url是 這樣做有什麼好處呢?顯而易見,瀏覽器在載入每一張的時候都會發起乙個http請求。如果使用css sprit...