JMM之Java中鎖概念的分類總結 - 池塘里洗澡的鴨子 - 博客園 (cnblogs.com)中介紹了JMM中鎖的分類,在 MySQL中鎖又有很多不同的分類:
1、從操作的粒度可分為表級鎖、行級鎖和頁級鎖。
表級鎖:每次操作鎖住整張表。鎖定粒度大,發生鎖沖突的概率最高,並發度最低。應用在MyISAM、InnoDB、BDB 等存儲引擎中。
行級鎖:每次操作鎖住一行數據。鎖定粒度最小,發生鎖沖突的概率最低,並發度最高。應用在InnoDB 存儲引擎中。
頁級鎖:每次鎖定相鄰的一組記錄,鎖定粒度界於表鎖和行鎖之間,開銷和加鎖時間界於表鎖和行鎖之間,並發度一般。應用在BDB 存儲引擎中。
2、從操作的類型可分為讀鎖和寫鎖。
讀鎖(S鎖):共享鎖,針對同一份數據,多個讀操作可以同時進行而不會互相影響。
寫鎖(X鎖):排他鎖,當前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖。
IS鎖、IX鎖:意向讀鎖、意向寫鎖,屬於表級鎖,S和X主要針對行級鎖。在對表記錄添加S或X鎖之前,會先對表添加IS或IX鎖。
S鎖:事務A對記錄添加了S鎖,可以對記錄進行讀操作,不能做修改,其他事務可以對該記錄追加S鎖,但是不能追加X鎖,需要追加X鎖,需要等記錄的S鎖全部釋放。X鎖:事務A對記錄添加了X鎖,可以對記錄進行讀和修改操作,其他事務不能對記錄做讀和修改操作。
3、從操作的性能可分為樂觀鎖和悲觀鎖。
樂觀鎖:一般的實現方式是對記錄數據版本進行比對,在數據更新提交的時候才會進行沖突檢測,如果發現沖突了,則提示錯誤信息。
悲觀鎖:在對一條數據修改的時候,為了避免同時被其他人修改,在修改數據之前先鎖定,再修改的控制方式。共享鎖和排他鎖是悲觀鎖的不同實現,但都屬於悲觀鎖范疇。
這些鎖如何實現?下面介紹分別介紹:
一、行鎖
在InnoDB引擎中,我們可以使用行鎖和表鎖,其中行鎖又分為共享鎖和排他鎖。InnoDB行鎖是通過對索引數據頁上的記錄加鎖實現的,主要實現算法有 3 種:Record Lock、Gap Lock 和 Next-key Lock。
RecordLock鎖:鎖定單個行記錄的鎖。(記錄鎖,RC、RR隔離級別都支持)
GapLock鎖:間隙鎖,鎖定索引記錄間隙,確保索引記錄的間隙不變。(范圍鎖,RR隔離級別支持)
Next-key Lock 鎖:記錄鎖和間隙鎖組合,同時鎖住數據,並且鎖住數據前后范圍。(記錄鎖+范圍鎖,RR隔離級別支持)
在RR隔離級別,InnoDB對於記錄加鎖行為都是先采用Next-Key Lock,但是當SQL操作含有唯一索引時,Innodb會對Next-Key Lock進行優化,降級為RecordLock,僅鎖住索引本身而非范圍。
1)select ... from 語句:InnoDB引擎采用MVCC機制實現非阻塞讀,所以對於普通的select語句,InnoDB不加鎖。
2)select ... from lock in share mode語句:追加了共享鎖,InnoDB會使用Next-Key Lock鎖進行處理,如果掃描發現唯一索引,可以降級為RecordLock鎖。
3)select ... from for update語句:追加了排他鎖,InnoDB會使用Next-Key Lock鎖進行處理,如果掃描發現唯一索引,可以降級為RecordLock鎖。
4)update ... where 語句:InnoDB會使用Next-Key Lock鎖進行處理,如果掃描發現唯一索引,可以降級為RecordLock鎖。
5)delete ... where 語句:InnoDB會使用Next-Key Lock鎖進行處理,如果掃描發現唯一索引,可以降級為RecordLock鎖。
6)insert語句:InnoDB會在將要插入的那一行設置一個排他的RecordLock鎖。
舉例說明:以“update t1 set name=‘XX’ where id=10”操作為例,分析 InnoDB 對不同索引的加鎖行為,以RR隔離級別為例。
1、主鍵加鎖:僅在id=10的主鍵索引記錄上加X鎖。
2、唯一鍵加鎖:現在唯一索引id上加X鎖,然后在id=10的主鍵索引記錄上加X鎖。
3、非唯一鍵加鎖:對滿足id=10的記錄和主鍵分別加X鎖,然后在(6,c)-(10,b)、(10,b)-(10,d)、(10,d)-(11,f)范圍分別加Gap Lock。
4、無索引加鎖:表里所有行和間隙都會加X鎖。(當沒有索引時,會導致全表鎖定,因為InnoDB引擎鎖機制是基於索引實現的記錄鎖定)。
二、悲觀鎖
悲觀鎖(Pessimistic Locking),是指在數據處理過程,將數據處於鎖定狀態,一般使用數據庫的鎖機制實現。從廣義上來講,前面提到的行鎖、表鎖、讀鎖、寫鎖、共享鎖、排他鎖等,這些都屬於悲觀鎖范疇(與JMM中悲觀鎖定義表述上區別還是比較大的,本質上還是一致的——排他)。
1)表級鎖
表級鎖每次操作都鎖住整張表,並發度最低。常用命令如下:
手動增加表鎖: lock table 表名稱 read|write,表名稱2 read|write;
查看表上加過的鎖:show open tables;
刪除表鎖: unlock tables;
表級讀鎖:當前表追加read鎖,當前連接和其他的連接都可以讀操作;但是當前連接增刪改操作會報錯,其他連接增刪改會被阻塞。
表級寫鎖:當前表追加write鎖,當前連接可以對表做增刪改查操作,其他連接對該表所有操作都被阻塞(包括查詢)。
即:表級讀鎖會阻塞寫操作,但是不會阻塞讀操作。而寫鎖則會把讀和寫操作都阻塞
2)共享鎖(行級鎖-讀鎖)
共享鎖又稱為讀鎖,簡稱S鎖。共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能修改。使用共享鎖的方法是在select ... lock in share mode,只適用查詢語句。
即:事務使用了共享鎖(讀鎖),只能讀取,不能修改,修改操作被阻塞。
3)排他鎖(行級鎖-寫鎖)
排他鎖又稱為寫鎖,簡稱X鎖。排他鎖就是不能與其他鎖並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能對該行記錄做其他操作,也不能獲取該行的鎖。使用排他鎖的方法是在SQL末尾加上for update,innodb引擎默認會在update,delete語句加上for update。行級鎖的實現其實是依靠其對應的索引,所以如果操作沒用到索引的查詢,那么會鎖住全表記錄。
即:事務使用了排他鎖(寫鎖),當前事務可以讀取和修改,其他事務不能修改,也不能獲取記錄鎖(select... for update)。如果查詢沒有使用到索引,將會鎖住整個表記錄。
三、樂觀鎖
樂觀鎖是相對於悲觀鎖而言的,它不是數據庫提供的功能,需要開發者自己去實現。在數據庫操作時,想法很樂觀,認為這次的操作不會導致沖突,因此在數據庫操作時並不做任何的特殊處理,即不加鎖,而是在進行事務提交時再去判斷是否有沖突了。
樂觀鎖實現的關鍵點:沖突的檢測。
悲觀鎖和樂觀鎖都可以解決事務寫寫並發,在應用中可以根據並發處理能力選擇區分,比如對並發率要求高的選擇樂觀鎖;對於並發率要求低的可以選擇悲觀鎖。
樂觀鎖實現原理:
1)使用版本字段(version)
先給數據表增加一個版本(version) 字段,每操作一次,將那條記錄的版本號加 1。version是用來查看被讀的記錄有無變化,作用是防止記錄在業務處理期間被其他事務修改。
2)使用時間戳(Timestamp)
與使用version版本字段相似,同樣需要給在數據表增加一個字段,字段類型使用timestamp時間戳。也是在更新提交的時候檢查當前數據庫中數據的時間戳和自己更新前取到的時間戳進行對比,如果一致則提交更新,否則就是版本沖突,取消操作。
樂觀鎖利用版本號實現的一個案例:
除了自己手動實現樂觀鎖之外,許多數據庫訪問框架也封裝了樂觀鎖的實現,比如hibernate框架。MyBatis框架大家可以使用OptimisticLocker插件來擴展。