淺析MySQL InnoDB的隔離級別

前言

仍是老規矩,首先提出兩個待解決的問題:mysql

  • MySQL InnoDB存儲引擎中事務的隔離級別有哪些?
  • 對應隔離級別的實現機制是什麼?

本文就將對上面這兩個問題進行解答,分析事務的隔離級別以及相關鎖機制。算法

隔離性簡介

隔離性主要是指數據庫系統提供必定的隔離機制,保證事務在不受外部併發操做影響的"獨立"環境執行,意思就是多個事務併發執行時,一個事務的執行不該影響其它事務的執行。sql

4種隔離級別介紹

SQL標準中定義了4種隔離級別,分別是:數據庫

  • Read uncommitted: 未提交讀,事務中的修改,即便沒有提交,對其餘事務也是可見的。存在髒讀
  • Read committed: 提交讀,大多數數據庫系統的默認隔離級別(MySQL不是), 一個事務從開始到提交以前,所作的修改對其餘事務不可見。解決髒讀,存在幻讀和不可重複讀
  • repeatable read: 可重複讀,該級別保證在同一事務中屢次讀取一樣記錄的結果是一致的。解決髒讀和不可重複讀,理論上存在幻讀,可是在InnoDB引擎中解決了幻讀
  • Serializable:可串行化,強制事務串行執行。

上面4種隔離級別是SQL標準定義的,可是在不一樣的存儲引擎中,實現的隔離級別不盡相同。本文主要介紹MySQL InnoDB 存儲引擎中的隔離級別,在InnoDB存儲引擎中,Repeatable Read 是默認的事務隔離級別,同時該引擎的實現基於多版本的併發控制協議——MVCC (Multi-Version Concurrency Control),解決了幻讀問題,固然 髒讀和不可重複讀也是不存在的。MVCC最大的好處就在於讀不加鎖,讀寫不衝突,這樣極大的增長了系統的併發性能bash

Read uncommitted

未提交讀,這種狀況下,一個事務A能夠看到另外一個事務B未提交的數據,若是此時事務B發生回滾,那麼事務A拿到的就是髒數據,這也就是髒讀的含義。此隔離級別在MySQL InnoDB通常不會使用,不作過多說明。併發

Read Committed

提交讀,一個事務從開始直到提交以前,所作的任何修改對其餘事務都是不可見的。解決了髒讀問題,可是存在幻讀現象。性能

所謂幻讀,指的是在同一事務下,連續執行兩次一樣的SQL語句可能致使不一樣的結果,第二次的SQL語句可能會返回以前不存在的行,也就是"幻行"測試

好比下面這個例子:優化

  1. 首先建立一張表,
CREATE TABLE `t` (
  `a` int(11) NOT NULL,
  PRIMARY KEY (`a`)
) ENGINE=InnoDB

insert into t(a) values(1);
insert into t(a) values(2);
insert into t(a) values(4);
複製代碼
  1. 分別執行事務1和事務2:

read_committed

能夠從上圖看出,Read Committed這種隔離級別存在幻讀現象。實際上,Read Committed還可能存在不可重複讀的問題,不可重複讀,指的是一個事務內根據同一條件對行記錄進行屢次查詢,可是查詢出的數據結果不一致,緣由就是查詢區間數據被其餘事務修改了ui

不可重複讀感受和幻讀有點像,實際上,前者強調是同一行記錄數據結果不同,後者強調的時屢次查詢返回的結果集不同,增長了或減小了

Repeatable Read

可重複讀,該級別保證在同一事務中屢次讀取一樣記錄的結果是一致的,在InnoDB存儲引擎中同時解決了幻讀和不可重複讀問題。至於InnoDB經過什麼方式解決幻讀和不可重複讀問題,後續內容揭曉。

Serializable (可串行化)

Serializable 是最高的隔離級別,它經過 強制事務串行執行,避免了幻讀的問題,可是 Serializable 會在讀取的每一行數據上都加鎖,因此可能致使大量的超時和鎖爭用的問題,所以併發度急劇降低,在MySQL InnoDB不被建議使用

Read Committed隔離級別下的加鎖分析

隔離級別的實現與鎖機制密不可分,因此須要引入鎖的概念,首先咱們看下InnoDB存儲引擎提供的兩種標準的行級鎖:

  • 共享鎖(S Lock):又稱爲讀鎖,能夠容許多個事務併發的讀取同一資源,互不干擾。即若是一個事務T對數據A加上共享鎖後,其餘事務只能對A再加共享鎖,不能再加排他鎖,只能讀數據,不能修改數據
  • 排他鎖(X Lock): 又稱爲寫鎖,若是事務T對數據A加上排他鎖後,其餘事務不能再對A加上任何類型的鎖,獲取排他鎖的事務既能讀數據,也能修改數據。

注意: 共享鎖和排他鎖是不相容的。

MySQL InnoDB存儲引擎是使用多版本併發控制的,讀不加鎖,讀寫不衝突,除非特定場景下的顯示加讀鎖(這裏不去探究)。本小節主要分析Read Committed隔離級別下的加鎖狀況,在MVCC的做用下,通常也就是寫操做加X鎖了。

加鎖操做是和索引緊密相關的,對一個SQL語句進行加鎖分析時,也要仔細考究其屬性列上的索引類型。假設有數據表t1,有兩個列,name列和id列,插入了幾條數據,沒有明確索引狀況:

insert into t1(name,id) values("a",10);
insert into t1(name,id) values("b",11);
insert into t1(name,id) values("c",13);
insert into t1(name,id) values("d",20);
複製代碼

下面執行 delete from t1 where id = 10 這條SQL語句,這裏的隔離級別設置爲Read Committed,從這條SQL語句不能得知id列的索引狀況,因此須要分狀況討論:

  • id列是主鍵
  • id列是二級惟一索引
  • id列是二級非惟一索引
  • id列上沒有索引

下面是對以上幾種狀況的加鎖狀況進行概括總結,更詳細的內容能夠參閱數據庫大牛的文章:MySQL 加鎖處理分析

id列是主鍵

id是主鍵時,上述SQL只須要在id=10這條記錄上加X鎖便可

id列是二級惟一索引

若id列是惟一索引,而主鍵是name列,那麼SQL須要加上兩個X鎖,一個對應於id索引上的id=10的記錄,另外一把鎖對應於主鍵索引上的[name="a",id=10]的記錄

id列是二級非惟一索引

若id列上有非惟一索引,那麼對應的全部知足SQL查詢條件的記錄,都會被加鎖,同時,這些記錄在主鍵索引上的記錄也會被加鎖。

id列上沒有索引

若id列上沒有索引,SQL會走聚簇索引的全掃描進行過濾,因爲過濾是由MySQL Sever層面進行的,所以每條記錄,不管是否知足條件,都會被加上X鎖。

Repeatable Read隔離級別下的加鎖分析

前面說過,在Repeatable Read隔離級別下,InnoDB存儲引擎解決了幻讀和不可重複讀問題,具體的原理是怎麼樣的呢?

以前簡短的介紹了InnoDB中行鎖的知識,下面來看下行鎖的三種算法:

  • Record Lock: 單個索引記錄上的鎖,即加X鎖
  • Gap Lock: 間隙鎖,鎖定一個範圍,但不包含記錄自身
  • Next-Key Lock: Gap Lock + Record Lock,鎖定一個範圍,而且鎖定自己。

Record Lock老是會去鎖住索引記錄,若是InnoDB存儲引擎在建表的時候沒有設置任何一個索引,那麼這時InnoDB會使用隱式的主鍵來進行鎖定。(表沒有定義主鍵的狀況,InnoDB會默認添加一個隱式的主鍵索引)

Next-Key Lock是結合了Gap Lock和Record Lock的一種鎖定算法,好比一個索引列有10,11,13和20這4個值,那麼該索引可能被Next-Key Locking的區間爲:

  • ($-\infty$,10)
  • (10,11]
  • (11,13]
  • (13,20]
  • (20,$+\infty$)

須要注意一點的是,當查詢的索引含有惟一屬性時,便是主鍵索引或者惟一索引時,InnoDB存儲引擎會對Next-Key Lock進行優化,將其降級爲Record Lock,即僅鎖住索引自己,通常加上X鎖。

Next-Key Lock機制設計的目的就是爲了解決幻讀問題,主要針對查詢列索引爲非惟一索引的時候。如下面這個例子進行說明:

  1. 首先建立測試表t1,name是主鍵索引,id爲非惟一索引,即輔助索引
CREATE TABLE `t1` (
  `id` int(11) NOT NULL,
  `name` varchar(200) DEFAULT NULL,
  PRIMARY KEY (`name`),
  KEY `id_indx` (`id`)
) ENGINE=InnoDB

insert into t1(name,id) values("a",10);
insert into t1(name,id) values("b",11);
insert into t1(name,id) values("c",13);
insert into t1(name,id) values("d",20);
複製代碼
  1. 執行 delete from t1 where id = 11,其加鎖狀況以下圖所示

gap_lock

這條SQL經過索引列id進行刪除操做,該索引爲非惟一索引,因此其使用傳統的Next-Key Locking 技術加鎖,而且因爲有主鍵索引和輔助索引兩個,須要分別進行鎖定。對於主鍵索引(即彙集索引),其僅對列name = "b"的索引加上 Record Lock,實際上就是X鎖。

而對於非惟一索引,其加上的時Next-Key Lock,鎖定範圍是(10,11),對其加上Gap Lock(間隙鎖),GAP鎖實際上就是加在兩條邊界記錄之間的位置。還須要注意的是,InnoDB還會對輔助索引下一個鍵值加上gap lock,即看到在(11,13)之間加了一個GAP鎖。對於11值自己加上Record Lock,即X鎖。

若此時開啓另一個事務執行下面的語句,就會阻塞:

1. select * from t1 where name = "b";
2. insert into t1(name,id) values("c",12);
複製代碼

好比第一條語句不能執行,由於在開始的事務中已經對彙集索引中的列name="b"的值加上了X鎖。所以執行會被阻塞。而第二個SQL,一樣不能執行,插入的值12在鎖定範圍(11,13)中,須要阻塞等待。

因此,從上例就能夠看出,GAP Lock的做用就是爲了阻止多個事務將記錄插入到同一範圍內,這樣就有效的解決了幻讀問題。

隔離級別總結

下面總結下InnoDB存儲引擎下的各類隔離級別:

隔離級別 髒讀可能性 不可重複讀可能性 幻讀可能性 加鎖讀
Read Uncommitted Yes Yes Yes No
Read Committed No Yes Yes No
Repeatable Read No No No No
Serializable No No No Yes

參考資料 & 鳴謝

相關文章
相關標籤/搜索