數據庫的悲觀鎖、樂觀鎖

 

併發控制

併發狀況下,須要作一些控制(通常是加鎖),保證共享數據的一致性。
 
併發操做數據庫時,須要給數據庫中的數據加鎖,確保數據庫中數據的一致性。
 
 
 
 

數據庫鎖的常見分類

按使用方式來分:悲觀鎖、樂觀鎖mysql

按鎖級別來分:共享鎖、排它鎖(主要是這2種,固然還有其餘的)sql

按鎖粒度來分:行級鎖、表級鎖、頁級鎖數據庫

 

 

 

悲觀鎖  Pessimistic Lock

悲觀的,假設是最壞的狀況,認爲其它線程必定會修改當前線程使用的數據庫數據,當前線程必定要給使用的數據庫數據加鎖。
 
悲觀鎖只是個統稱,並非指某一種具體的鎖。悲觀鎖主要包括:
  • 共享鎖(S鎖,share),又稱爲讀鎖,全部線程均可以訪問,但都只能讀
  • 排它鎖(X鎖),又稱爲寫鎖,是排它的,同一時刻只能有一個線程來訪問,這個線程可對加鎖的數據進行讀寫。
Java中的 synchronized、 ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

 

悲觀鎖通常要藉助數據庫自己提供的鎖機制來實現。併發

以mysql最經常使用的InnoDB引擎爲例:加排它鎖高併發

begin;  //開始事務
select * from tb_user where id=1 for update;  //給選中的行加鎖
update tb_user set username='chy',password='abcd' where id=1;  //修改數據
commit;  //提交事務

要開啓事務,不必定非要用mysql的begin、commit,好比可使用Spring的事務管理。性能

先select...for update 鎖定要使用的行,再修改數據。優化

InnoDB默認使用行級鎖,鎖定要使用的行;但行級鎖是基於索引的,若是sql語句用不到索引,會使用表級鎖將整張表鎖住。spa

 

 

 

 

樂觀鎖   Optimistic Lock

樂觀的,假設是很好的狀況,認爲通常不會發生衝突,只在提交更新時進行衝突檢測。線程


樂觀鎖不須要藉助數據庫自身的鎖機制來實現。樂觀鎖常見的實現方式:設計

一、CAS方式

select quantity from tb_goods where id=1;  //先查詢該種商品的庫存,假設爲10
update tb_goods set quantity=quantity-1 where id=1 and quantity=10;  //提交修改時帶上條件庫存等於10,確保數據沒有被修改

CAS 即 Compare and Swap,先和數據庫中的quatity比較,若是quantity等於先前查詢到的值(10),說明記錄沒有被修改,執行操做。

 

CAS方式可能會產生ABA問題:

開始查到庫存爲10,有一個線程將庫存改成了9(好比售出1件),而後又有一個線程將庫存改回了10(好比買家不滿意,退貨了),庫存仍是原來的值,但數據已經被改過了。

 

且選擇做爲比較的那些字段不必定能標識這條記錄是否已被修改。

 

 

二、版本號機制(推薦)

select version from tb_goods where id=1;  //查詢這條記錄的數據版本號,假設爲5
update tb_goods set quantity=quantity-1,version=version+1 where id=1 and version=5;  //提交更新時檢測版本號是否一致

設計表時額外增長version列,每次更新一條記錄時都將這條記錄的version+1,執行更新操做時先查詢這條記錄的version,提交更新時比較version是否和查詢到的相同,相同就說明數據未被修改,就會提交更新。

 

 

 

 

悲觀鎖、樂觀鎖的比較、選擇

比較:悲觀鎖是必定要加鎖,樂觀鎖實際上並無加鎖。

 

樂觀鎖的2種實現都有個問題:

樂觀鎖是假設在本線程訪問數據庫數據時,其它線程不會修改這部分數據。

而併發量大的時候,你查到version=5,其餘線程每每會修改當前線程使用的數據庫數據,修改version,由於沒加鎖,其餘線程也能夠訪問當前線程使用的數據庫數據。更新的時候很容易出現更新不了的狀況。

 

就是說樂觀鎖適合併發量小的狀況使用,那爲何在高併發的狀況下也會使用樂觀鎖?由於效率|性能。

悲觀鎖是每次都要加鎖,悲觀鎖保證了數據的一致性,更新成功機率高,但效率低下。

樂觀鎖實際沒有加鎖,更新成功機率要低一些,尤爲是高併發的時候,但每次都不加鎖,效率高、性能好。

 

面對高併發,首先要能扛住,抗都扛不住,不少請求都不能及時處理,談什麼操做成功率。

抗住了,就算更新失敗,好歹用戶知道請求處理了、只是操做失敗了;沒抗住,用戶請求半天沒響應,連處理都還沒處理。

 

選擇:

併發量小,悲觀鎖、樂觀鎖的更新成功率都高,但悲觀鎖加了鎖,更新成功率更高,優先使用悲觀鎖;

併發量大,使用樂觀鎖,優先考慮性能。

 

 

 

 

樂觀鎖的優化寫法

update tb_goods set quantity=quantity-1 where id=1 and quantity-1>=0; 

只要庫存夠就行,管它其餘線程修不修改,反正只有一條sql語句,不涉及事務。

 

這種寫法有要求:

數據庫操做要只有一條sql語句,若是有多條sql語句,執行起來須要時間,這期間可能其餘線程修改了當前線程要使用的數據。

相關文章
相關標籤/搜索