【每日鮮蘑】從資料庫看樂觀鎖、悲觀鎖

襄垣發表於2020-03-27

樂觀鎖悲觀鎖主要是用於解決併發問題的,而且是比較低階的併發問題。

場景

一般是多個使用者對同一資源進行處理時,會出現併發問題。比如,一篇文章,使用者進行了點贊操作。我們的業務處理一般如下:

步驟 操作 資料庫語句舉例
1 查詢這篇文章 select id, praise_points from t_article where id = 1
2 使用者ID文章ID的點贊關係寫入點贊關係表中 insert into t_praise_link (id, user_id, article_id) values (...)
其他執行緒 更新了文章的點贊數 ……
3 更新文章的點贊數 update t_article set praise_points = ? where id = 1

此時是不加鎖的,在高併發時,會出現文章表記錄的點贊數比實際點贊數少的情況。下面我們使用加鎖的方式來解決這個併發問題。

悲觀鎖

總假設最壞的情況,所以每次拿【select】時總是上鎖,不允許其他執行緒修改。資料庫中的行鎖表鎖共享鎖排他鎖、Java 中的synchronized都屬於悲觀鎖的範疇。

資料庫加鎖的實現方式

鎖型別 實現舉例
共享鎖 select id, praise_points from t_article where id = 1 lock in share mode
排他鎖 select id, praise_points from t_article where id = 1 for update
排他鎖 innoDB 引擎下,update,insert,delete 預設自動加了排他鎖

應用悲觀鎖

首先分析應該用共享鎖(允許其它事務也增加共享鎖讀取,但不允許其它事務修改或者加入排他鎖)還是排他鎖,這很重要。首先,我們看此時的業務場景,我們鎖定的資料和我們修改的資料都是文章表,此時使用共享鎖就不合適了,容易出現死鎖。原因是:共享鎖,事務都加,都能讀。修改是惟一的,必須等待前一個事務commit,才可

步驟 操作 資料庫語句舉例
begin 開始事務
1 查詢這篇文章(加排他鎖) select id, praise_points from t_article where id = 1 for update
2 使用者ID文章ID的點贊關係寫入點贊關係表中 insert into t_praise_link (id, user_id, article_id) values (...)
3 更新文章的點贊數 update t_article set praise_points = ? where id = 1
end 結束事務

樂觀鎖

在更新的時候才會去判斷一下別人有沒有去更新這個資料。

應用樂觀鎖

一般會使用版本號機制CAS演算法(潛在ABA問題)實現。最常用的是版本號機制,主要是實現起來比較簡單,常用的ORM都有完善的實現機制。

步驟 操作 資料庫語句舉例
begin 開始事務
1 查詢這篇文章(加排他鎖) select id, praise_points, version from t_article where id = 1
2 使用者ID文章ID的點贊關係寫入點贊關係表中 insert into t_praise_link (id, user_id, article_id) values (...)
3 更新文章的點贊數 update t_article set praise_points = ? where id = 1 and version = 1
end 結束事務

總結

本文基於資料庫層面簡單介紹了樂觀鎖悲觀鎖的概念,但在開發生活中,鎖的種類是非常多的,比如偏向鎖輕量級鎖重量級鎖間隙鎖等等,針對不同的併發問題,其實解決方法都是不一樣的,但還是有一些巨人們積累的經驗可供借鑑。

  1. 悲觀鎖適合寫多讀少的場景;
  2. 樂觀鎖適合寫少讀多的場景;
  3. 阿里巴巴的建議:如果每次訪問衝突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖樂觀鎖的重試次 數不得小於 3 次;
  4. 控制好鎖的範圍,減小鎖定物件的範圍,比如使用行鎖。

本文使用 mdnice 排版

相關文章