按使用方式來分:悲觀鎖、樂觀鎖mysql
按鎖級別來分:共享鎖、排它鎖(主要是這2種,固然還有其餘的)sql
按鎖粒度來分:行級鎖、表級鎖、頁級鎖數據庫
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
樂觀的,假設是很好的狀況,認爲通常不會發生衝突,只在提交更新時進行衝突檢測。線程
樂觀鎖不須要藉助數據庫自身的鎖機制來實現。樂觀鎖常見的實現方式:設計
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語句,執行起來須要時間,這期間可能其餘線程修改了當前線程要使用的數據。