本文首發於 vivo互聯網技術 微信公衆號
連接: https://mp.weixin.qq.com/s/S7MhlsZveBHRSQhq5aTIJA
做者:何志創
通常你們對數據庫事務的瞭解可能停留在事務的ACID特性以及事務4種不一樣的隔離級別層面上,而對於事務 4 種不一樣隔離級別如何實現瞭解相對較少。數據庫
本文以 MySQL 數據庫 InnoDB 引擎爲例,爲你們分析 InnoDB數據庫引擎對默認的隔離級別可重複讀(RR)的具體實現。微信
整文知識點介紹:事務4種隔離級別、不一樣隔離級別解決的問題、MVCC、鎖的類型、加鎖案例分析;閱讀完整文相信你們對事務隔離級別的具體實現有了必定的認識。併發
(1)未提交讀(Read uncommitted):一個事務讀取到其餘事務未提交的數據,是級別最低的隔離機制;spa
(2)提交讀(Read committed):一個事務讀取到其餘事務提交後的數據;blog
(3)可重複讀(Repeatable read):一個事務對同一份數據讀取到的相同,不在意其餘事務對數據的修改;索引
(4)序列化(Serializable) :事務串行化執行,隔離級別最高,犧牲了系統的併發性。事務
若不考慮事務的隔離級別,則事務的併發會形成如下問題:rem
(1)髒讀:事務A讀取了事務B更新的數據,而後B回滾操做,那麼A讀取到的數據是髒數據。get
(2)不可重複讀:事務 A 屢次讀取同一數據,事務 B 在事務A屢次讀取的過程當中,對數據做了更新並提交,致使事務A屢次讀取同一數據時,結果 不一致。it
(3)幻讀:同一事務中對同一範圍的數據進行讀取,結果卻多出了數據或者少了數據,這就叫幻讀。(如同一事務對id<10的範圍進行2次查詢,第一次出現id=八、9的兩條數據,第二次出現id=七、八、9的3條數據)。
不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住知足條件的行,解決幻讀須要鎖表。
不一樣的隔離級別針對上述3個問題的解決能力,以下表:
上文提到 InnoDB 默認的隔離級別是可重複讀(RR),InnoDB是經過MVCC(多版本併發控制)來實現可重複讀的,下面爲你們介紹MVCC。
在InnoDB中,給每行增長兩個隱藏字段來實現MVCC,一個用來記錄數據行的建立時間,另外一個用來記錄行的過時時間(刪除時間)。在實際操做中,存儲的並非時間,而是事務的版本號,每開啓一個新事務,事務的版本號就會遞增。
因而乎,默認的隔離級別(REPEATABLE READ)下,增刪查改變成了這樣:
(1)SELECT
(2)INSERT
(3)UPDATE
(4)DELETE
(1)快照讀:讀取的是快照版本,也就是歷史版本;
(2)當前讀:讀取的是最新版本。
普通的SELECT就是快照讀,而UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE是當前讀。
(3)結論:若是隔離級別是REPEATABLE READ,那麼在同一個事務中的全部普通select讀讀到的都是事務第一個讀到的快照,如此實現了可重複讀;而對於當前讀(UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE),InnoDB 經過加鎖來實現可重複讀,且InnoDB 加鎖同時解決了幻讀問題。
InnoDB 引入如下三種鎖類型:
假設一個索引包含如下幾個值:10,11,13,20。那麼這個索引的next-key鎖將會覆蓋如下區間:(-oo, 10]、(10, 11]、(11, 13]、(13, 20]、(20, +oo)。
MySQL InnoDB 經過間隙鎖解決了幻讀問題。如下經過實際的案例分析來介紹InnoDB 是若是解決幻讀問題的。
在對SQL進行加鎖分析前,須要明確表的結構和索引類型。在不知道索引的狀況下直接給出一條SQL來分析若是加鎖是沒有任何意義的。
如下以用戶表(t_user)爲例(id爲主鍵,name爲惟一索引,age爲通常索引,address無索引)分析不一樣索引條件的加鎖表現。
例:delete from t_user where id=120;
條件爲主鍵,此時鎖住聚簇索引中對應的行記錄:即Record Locks鎖住id=120的行記錄。
此種狀況下,其餘事務除了不能刪除、更新此條記錄外,其餘插入其餘行、更新其餘行都行。
SQL驗證:
例:delete from t_user where name='n20';
條件爲惟一索引,鎖住索引記錄,同時鎖住聚簇索引中的對應行記錄:
SQL驗證:
例:delete from t_user where age=20;
與主鍵和惟一索引不一樣的是,通常索引的記錄是容許重複的;換句話說,若是咱們單純地給索引加記錄鎖時,其餘事務依然能夠插入,也就有可能出現幻讀問題了。
因此除了給對應索引記錄加上記錄鎖以外,還要給Gap加上鎖。
從上面知識點咱們能夠預估這個操做一共須要的鎖:
20_120, 20_130(如下均用age_id這種形式表示索引值)
(10, 20)、(20, 20)、(20, 40)
id=120/130對應的行記錄
SQL驗證:
根據實際狀況,3-6均符合咱們預期,然而7和8則超出了咱們預期的鎖範圍。爲何會超出咱們預期呢?這次咱們進行分析一下:
從七、8插入語句來看,因爲id爲自增主鍵,會自動遞增,語句7插入值預計爲:10_141;
語句8插入值預計爲:40_141,爲何只有後者能插入呢?
其實咱們能夠將B+樹中的間隙理解得更加精準一點:
age=20的三個間隙應該爲:(10_110, 20_120)、(20_120, 20_130)、(20_130, 40_140);
從上圖能夠看出語句7插入值10_141 沒法插入,由於間隙被鎖住了;而語句8插入 40_141值由於在間隙以外了,無鎖衝突,容許插入。
因此最終的加鎖狀況應該這樣表示:
delete from t_user where address='a20',由於沒法精準定位,InnoDB選擇將聚簇索引中的全部行以及間隙都鎖起來,功能上已經等於鎖表了:
SQL驗證:
InnoDB 在RC(READ COMMITTED)隔離級別中,只會在對應的索引/行記錄上加Record Lock,而不會加Gap鎖,緣由也很簡單,由於該隔離級別是容許存在幻讀問題的。
在RR級別下的加鎖方式稱之爲Next-Key Locks,其實就是上述Record Locks和Gap Locks的結合。好比Gap Lock爲(10,20) ,record lock爲20,結合的Next-Key lock 爲:(10, 20]。
分析Next-Key Locks其實就是要分析Record Locks和Gap Locks。MySQL InnoDB的可重複讀並不保證避免幻讀,須要應用使用加鎖讀來保證。而這個加鎖讀使用到的機制就是next-key locks。
若是使用普通的讀,會獲得一致性的結果,若是使用了加鎖的讀,就會讀到「最新的」「提交」讀的結果。自己,可重複讀和提交讀是矛盾的。在同一個事務裏,若是保證了可重複讀,
就會看不到其餘事務的提交,違背了提交讀;若是保證了提交讀,就會致使先後兩次讀到的結果不一致,違背了可重複讀。能夠這麼講,InnoDB提供了這樣的機制,在默認的可重複讀的隔離級別裏,可使用加鎖讀去查詢最新的數據。
更多內容敬請關注 vivo 互聯網技術 微信公衆號
注:轉載文章請先與微信號:labs2020 聯繫。