淺析樂觀鎖與悲觀鎖


悲觀鎖

當我們要對數據庫中的一條數據進行修改的時候,為了避免同時被其他人修改,最好的辦法就是直接對該數據進行加鎖以防止並發。這種借助數據庫鎖機制在修改數據之前鎖定,再修改的方式被稱為悲觀並發控制(PCC)。

之所以叫做悲觀鎖,是因為抱有悲觀的態度去修改數據的並發控制方式,認為數據並發修改的概率比較大,所以需要在修改之前先加鎖。

悲觀並發控制實際上是 “先取鎖,再訪問” 的保守策略,為數據處理的安全提供了保證。

 

簡單實踐樂觀鎖與悲觀鎖
 

 

在效率上,處理加鎖的機制會讓數據庫產生額外的開銷,還會有死鎖的可能性。降低並行性,一個事務如果鎖定了某行數據,其他事務就必須等待該事務處理完才可以處理那行數據。

悲觀鎖的實現方式:悲觀鎖的實現,依靠數據庫提供的鎖機制。在數據庫中,悲觀鎖的流程如下:

  • 在對數據修改前,嘗試增加排他鎖。
  • 加鎖失敗,意味着數據正在被修改,進行等待或者拋出異常。
  • 加鎖成功,對數據進行修改,提交事務,鎖釋放。
  • 如果我們加鎖成功,有其他線程對該數據進操作或者加排他鎖的操作,只能等待或者拋出異常。
 

樂觀鎖

樂觀鎖是相對悲觀鎖而言的,樂觀鎖假設數據一般情況下不會造成沖突,所以在數據進行提交更新的時候,才會正式對數據的沖突與否進行檢測。

相對於悲觀鎖,在數據庫進行處理的時候,樂觀鎖不會使用數據庫提供的鎖機制,一般是增加 version 參數,記錄數據版本

 

簡單實踐樂觀鎖與悲觀鎖
 

 

樂觀並發控制相信事務之間的數據競爭概率非常小,因此盡可能直接操作,提交的時候才去鎖定,不會產生任何鎖和死鎖。

 

上手試一試

基於 MySQL InnoDB 引擎

 

使用悲觀鎖

begin; select quantity from products where id = 1 for update; update products set quanntity = 2 where id = 1; commit;

以上,對 id 為 1 的產品進行修改,先通過 for update 的方式進行加鎖,然后再修改。典型的悲觀鎖策略。

如果修改庫存的邏輯發生並發,同一時間只有一個線程可以開啟事務並獲得 id = 1 的鎖,其他事務必須等本次提交之后才能執行,這樣可以保證數據不被其他事務修改。

使用排他鎖會把數據鎖住,不過需要注意一些基本的鎖級別,MySQL InnoDB 默認行級鎖。行級鎖是基於索引的,如果一條 SQL 語句用不到索引是不會使用行級鎖,會使用表級鎖把整張表鎖住。

 

使用樂觀鎖

select quantity from products where id = 1 update products set quantity = 2 where id = 1 and quantity = 3

先查詢庫存表當前庫存數,然后更新的時候判斷數據表對應數據的 quantity 與第一次取出來的是否一致,一致則更新,否則認為是過期數據。

這樣實現有一個問題,線程 1 從數據庫取出 quantity 為 3,線程 2 也取出同一條數據的 quantity,進行操作,變成了 2,然后又進行某些操作 變成了 3,此時線程 1 進行更新操作成功。但是這個過程有問題。

引入 version 參數,樂觀鎖每次在執行數據修改的操作,都會帶上版本號,一旦版本號和數據的版本號一致就可以執行修改操作並對 version 執行 +1 操作,否則就執行失敗。

這樣實現也有一個問題,如果真的有高並發的時候,就只有一個線程可以修改成功,就會存在大量的失敗。

如果你的應用存在超高並發,這樣解決也不好,因為會總讓用戶感知到失敗。

嘗試減小樂觀鎖力度,最大程度提高吞吐。

update products set quantity = quantity - 1 where id = 1 and quantity - 1 > 0

使用這條 SQL 語句,在執行過程中,會在一次原子操作中查詢一遍 quantity 的值,並且減去 1。

 

簡述區別

  1. 樂觀鎖不是真的加鎖,效率高,但是要控制好鎖的力度。
  2. 悲觀鎖依賴數據庫鎖,效率低。
 

總結

無論是悲觀鎖還是樂觀鎖,都是人們定義出來的概念,可以認為是一種思想。

大家要記住鎖機制一定要在事務中才能生效哦。

以上是我對樂觀鎖與悲觀鎖一點基礎實踐,希望能和大家再深入了解了解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM