樂觀鎖
和悲觀鎖
主要是用於解決併發問題的,而且是比較低階的併發問題。
場景
一般是多個使用者對同一資源進行處理時,會出現併發問題。比如,一篇文章,使用者進行了點贊操作。我們的業務處理一般如下:
步驟 | 操作 | 資料庫語句舉例 |
---|---|---|
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 | 結束事務 |
總結
本文基於資料庫層面簡單介紹了
樂觀鎖
和悲觀鎖
的概念,但在開發生活中,鎖的種類是非常多的,比如偏向鎖
、輕量級鎖
、重量級鎖
、間隙鎖
等等,針對不同的併發問題,其實解決方法都是不一樣的,但還是有一些巨人們積累的經驗可供借鑑。
悲觀鎖適合寫多讀少的場景; 樂觀鎖適合寫少讀多的場景; 阿里巴巴的建議:如果每次訪問衝突概率小於 20%
,推薦使用樂觀鎖
,否則使用悲觀鎖
。樂觀鎖
的重試次 數不得小於3
次;控制好鎖的範圍,減小鎖定物件的範圍,比如使用行鎖。
本文使用 mdnice 排版