資料庫和MVCC多版本併發控制基本介紹可以參考之前的文章 資料庫和MVCC多版本併發控制。
本文所有內容基於MySQL/InnoDB引擎。
併發事務解決方案
髒讀、不可重複讀和幻讀都是資料庫讀一致性問題,需要由資料庫提供一定的事務隔離機制來解決。
(1)鎖機制
解決寫-寫衝突問題。在讀取資料前,對其加鎖,防止其它事務對該資料進行修改。
-
悲觀鎖
往往依靠資料庫提供的鎖機制。
-
樂觀鎖
大多是基於資料版本記錄機制來實現。
(2)MVCC多版本併發控制
解決讀-寫衝突問題。不用加鎖,通過一定機制生成一個資料請求時間點時的一致性資料快照, 並用這個快照來提供一定級別 (語句級或事務級) 的一致性讀取。這樣在讀操作的時候不需要阻塞寫操作,寫操作時不需要阻塞讀操作。
MySQL/InnoDB鎖機制
加鎖方式
-
兩段鎖
加鎖/解鎖是兩個不相交的階段,加鎖階段:只加鎖,不解鎖。解鎖階段:只解鎖,不加鎖。
鎖型別
鎖粒度
按照鎖粒度維度來看,MySQL資料庫根據不同的儲存引擎可以有表級鎖、頁級鎖和行級鎖。
表級鎖
MySQL中鎖定粒度最大的一種鎖,對當前操作的整張表加鎖,實現簡單,資源消耗也比較少,加鎖快,不會出現死鎖。其鎖定粒度最大,觸發鎖衝突的概率越高,併發度最低,MyISAM和InnoDB引擎都支援表級鎖。
行級鎖
Mysql中鎖定粒度最小的一種鎖,只針對當前操作的行進行加鎖。行級鎖能大大減少資料庫操作的衝突。其加鎖粒度最小,併發度高,但加鎖的開銷也最大,加鎖慢,會出現死鎖。
雖然使用行級鎖具有粒度小、併發高等特點,但是表級鎖在某些場景下也是必需的。
- 事務更新大表中的大部分資料時直接使用表級鎖效率更高,避免頻繁加行級鎖。
- 事務比較複雜,使用行級鎖很可能會導致死鎖導致回滾。
鎖的相容性
按照是否相容來分類,表級鎖和行級鎖可以再細分為共享鎖和排它鎖。
共享鎖
共享鎖(Share Locks,簡記為S鎖)又稱為讀鎖。其它事務可以併發地讀取資料,可以再加共享鎖,但任何事務都不能獲取資料上的排它鎖,直至已經釋放所有共享鎖。
排它鎖
排它鎖(Exclusive lock,簡記為X鎖)又稱為寫鎖。若事務對資料物件加上了排它鎖,則只允許該事務對資料物件進行讀取和修改,其它事務不能再對資料物件加任何型別的鎖,直到該事務釋放物件上的排它鎖。在更新操作(INSERT、UPDATE 或 DELETE)過程中始終應用排它鎖。
意向鎖
為了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。
-
意向共享鎖(IS)
表示事務準備給資料行加入共享鎖,事務在給一個資料行加共享鎖之前必須先取得該表的IS鎖。
-
意向排它鎖(IX)
表示事務準備給資料行加入排它鎖,事務在給一個資料行加排它鎖之前必須先取得該表的IX鎖。
意向鎖的作用
MySQL中表鎖和行鎖共存,若不引入意向鎖,該如何判斷是否鎖衝突呢?
假設事務T要對錶T1加X鎖,那就必須要判斷T1表下每一個資料行是否加了S鎖或者X鎖。這樣做的效率會非常低,需要對整個表進行遍歷。在引入意向鎖之後情況變得簡單了。
假設事務T要對錶T1加X鎖,在這之前假設已經有事務A對資料行R加了S鎖,那麼此時表上已經有IS鎖了(事務在給一個資料行加S鎖之前必須先取得該表的IS鎖)。由於X鎖和IS鎖衝突,所以事務T需要等待鎖操作完成。這樣就省去了遍歷的操作,提高了衝突判斷效率。
注意事項
- 意向鎖是表鎖,表示的是一種意向,僅僅表示事務正在讀或寫某一行記錄,在真正加行鎖時才會判斷是否衝突。意向鎖是InnoDB自動加的,不需要使用者干預。
- IX和IS是表鎖,不會與行鎖發生衝突,只會與表鎖發生衝突。
相容情況
InnoDB的鎖相容情況如下所示。
鎖演算法
InnoDB主要實現了三種行鎖演算法。
-
Record Lock
記錄鎖,鎖定一個行記錄
-
Gap Lock
間隙鎖,鎖定一個區間
-
Next-Key Lock
記錄鎖+間隙鎖,鎖定行記錄和區間
Next-Key Lock
Gap Lock和Next-Key Lock是為了解決幻讀問題。
我們知道MVCC裡面Repeated Read級別是快照讀,read view是在執行事務中第一條select語句的瞬間建立,後續所有的select都是複用這個物件,所以能保證每次讀取的一致性。可是這並不能確保就不會出現幻讀問題了,仍然可能在執行insert/update時遇到幻讀現象,因為SELECT 不加鎖的快照讀行為是無法限制其他事務對新增重合範圍的資料的插入的。可能會出現這樣的情況,select出了2條記錄,update的時候卻返回了3個成功結果。
InnoDB通過間隙鎖來鎖定區間間隔,避免其它事務在這個區間內插入其它資料導致出現幻讀現象。
在MySQL的規範裡面RR事務級別是可能出現幻讀的,InnoDB通過間隙鎖避免了這種情況。這個實現和規範有所差別。另外,Next-Key Lock是Repeated Read級別才會有的,在Read Committed級別並不存在。
示例
假設表T1(id private key),一共有3條記錄1、3、5,同時有兩個事務在進行。
事務A在T2時刻查詢的結果為5,由於使用select … for update語句,這會在區間(3,+∞)這個範圍內加上Gap Lock,因此在這個區間內的插入都是不被允許的。所以T6時刻查詢結果也是5,避免了幻讀現象。事務B在T4時刻想插入4,這個屬於(3,+∞)區間,因此寫入會被阻塞,直到事務A結束。