天天看點

Mysql之Innodb鎖模式和死鎖解析一. 背景知識二、鎖模式三、死鎖

一. 背景知識

InnoDB與MyISAM的最大不同有兩點:一是支援事務(TRANSACTION);二是采用了行級鎖。由于後面的死鎖問題基本都是并發事務的前提下發生,是以我們先了解一下事務的相關知識。

1、事務的ACID屬性

事務是由一組SQL語句組成的邏輯處理單元,事務具有以下4個屬性,通常簡稱為事務的ACID屬性。

原子性(Atomicity):事務是一個原子操作單元,其對資料的修改,要麼全都執行,要麼全都不執行。

一緻性(Consistent):在事務開始和完成時,資料都必須保持一緻狀态。這裡的“一緻” 包含兩個層面,一個是資料庫層面的資料完整性限制,比如所有的内部資料結構(如B樹索引或雙向連結清單)必須是正确的。另一個層面是應用層的邏輯限制,比如銀行的轉賬一方加了100元,那麼另一方必須減100元(數額不能多也不能少)。

隔離性(Isolation):資料庫系統提供一定的隔離機制,保證事務在不受外部并發操作影響的“獨立”環境執行。這意味着事務處理過程中的中間狀态對外部是不可見的,反之亦然。

持久性(Durable):事務完成之後,它對于資料的修改是永久性的,即使出現系統故障也能夠保持。

 2、并發事務處理帶來的問題

相對于串行處理來說,并發事務處理能大大增加資料庫資源的使用率,提高資料庫系統的事務吞吐量,進而可以支援更多的使用者。但并發事務處理也會帶來一些問題,主要包括以下幾種情況。

髒讀(Dirty Reads):一個事務正在對一條記錄做修改,在這個事務完成并送出前,第二個事務讀取了這些“髒”資料,并據此做進一步的處理,就會産生未送出的資料依賴關系。這種現象被形象地叫做"髒讀"。

不可重複讀(Non-Repeatable Reads):一個事務在讀取某些資料後的某個時間,再次讀取以前讀過的資料,卻發現其讀出的資料已經發生了改變,這種現象就叫做“不可重複讀”。

幻讀(Phantom Reads):一個事務按相同的查詢條件重新讀取以前檢索過的資料,卻發現其他事務插入了滿足其查詢條件的新資料,這種現象就稱為“幻讀”。

3、事務的隔離級别 

上述并發問題中,“髒讀”、“不可重複讀”和“幻讀”,其實都是資料庫讀一緻性問題,必須由資料庫提供一定的事務隔離機制來解決。ISO/ANSI SQL92定義了4個事務隔離級别:

未送出讀(Read Uncommitted):最低的隔離級别,一個事務可以讀到另一個事務未送出的結果。所有的并發事務問題都會發生。

已送出讀(Read Committed):隻有在事務送出後,其更新結果才會被其他事務看見。可以解決髒讀問題,但兩次讀取同一份資料可能看到不同的結果(存在不可重複讀問題)。

可重複讀(Repeated Read):在一個事務中,對于同一份資料的讀取結果總是相同的(快照讀,通過資料多版本并發控制實作,簡稱MVCC,也就是對目前的資料Copy一個版本,以後的讀操作從這個版本中讀取),無論是否有其他事務對資料進行操作,以及這個事務是否送出。可以解決髒讀、不可重複讀問題。

注意:存在幻讀問題,可重複讀隻是針對普通的查詢語句,1、若查詢語句顯示的加上鎖如,lock in share mode或者for update,則此次查詢會放棄Copy版本(快照讀),采用目前最新版本(目前讀),進而可以讀到其它事務送出後(包括更新和插入)的最新資料;2、若目前事務恰好更新了本事務内的資料和其它事務送出後的資料,那麼被更新的資料會同步到Copy版本(更新快照),是以之後的查詢均可查詢到被更新的資料。

序列化(Serializable):事務串行化執行,隔離級别最高,犧牲了系統的并發性。可以解決并發事務的所有問題。

二、鎖模式

1、意向鎖

InnoDB預設使用行鎖,行鎖是對索引加的鎖,而不是針對記錄加的鎖。是以,在不通過索引條件查詢的時候,使用的是表鎖。不管是表鎖還是行鎖,都有如下兩種鎖模式:

共享鎖(S):允許一個事務去讀資料,阻止其他事務獲得相同資料集的排他鎖。

排他鎖(X):允許獲得排他鎖的事務更新資料,阻止其他事務取得相同資料集的共享讀鎖和排他寫鎖。

另外,為了提高表鎖和行鎖沖突檢測效率,InnoDB提供了如下兩種意向鎖(Intention Locks),這兩種意向鎖都是表鎖,是InnoDB在擷取行鎖前自動加上的:

意向共享鎖(IS):事務打算給資料行加行共享鎖,事務在給一個資料行加共享鎖前必須先取得該表的IS鎖。

意向排他鎖(IX):事務打算給資料行加行排他鎖,事務在給一個資料行加排他鎖前必須先取得該表的IX鎖。

如果一個事務請求的鎖模式與目前的鎖相容,InnoDB就将請求的鎖授予該事務;反之,如果兩者不相容,該事務就要等待鎖釋放。那麼鎖與鎖的“沖突-相容”規則是怎樣的呢?

2、“沖突-相容”規則

 引用某知乎的圖檔如下。

Mysql之Innodb鎖模式和死鎖解析一. 背景知識二、鎖模式三、死鎖

思考1:意向鎖是怎麼提高檢測效率的?

答案:在加表鎖的時可立即檢測到是否存在意向鎖,若存在,則說明有行鎖,是以無須周遊整個表即可獲得沖突結果。

思考2:為什麼IX 與 X會産生沖突?與行級鎖沖突?

答案:IX,IS是表級鎖,不會和行級的X,S鎖發生沖突,鎖的沖突是按級分開的。

3、加鎖機制 

InnoDB支援行鎖、間隙鎖、插入間隙意向鎖和後碼鎖:

1)行鎖(Record Lock)

行鎖是加在某一行資料對應的索引上,對于UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及資料集索引加排他鎖(X);對于普通SELECT語句,InnoDB不會加任何鎖,是以也無需等待鎖(Create Table And Select, CATS 語句除外,CATS中的查詢語句會隐式加上共享鎖,原因是為了保證主從複制的正确性);普通SELECT可通過如下形式顯示給資料集索引加共享鎖或排他鎖:

共享鎖(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

排他鎖(X):SELECT * FROM table_name WHERE ... FOR UPDATE

注意:即便在條件中使用了索引字段,但是否使用索引來檢索資料是由MySQL通過判斷不同執行計劃的代價來決定的,如果MySQL認為全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下InnoDB将使用表鎖,而不是行鎖。是以,在分析加鎖機制時,别忘了檢查SQL的執行計劃,以确認是否真正使用了索引。比如,檢索值的資料類型與索引字段不同,雖然MySQL能夠進行資料類型轉換,但卻不會使用索引,進而導緻InnoDB使用表鎖。

2)間隙鎖(Gap Lock)

在事務隔離級别為可重複讀的情況下,InnoDB還會出現間隙鎖(Gap Lock),間隙鎖是指加在索引之間的鎖(開區間,不包括索引記錄本身)。

由于可以阻止其它事務在索引間隙中插入新資料,是以,間隙鎖可避免一部分幻讀的産生,當然也可以通過如下語句來關閉間隙鎖:

set innodb_locks_unsafe_for_binlog = ‘on’

我們可通過如下範圍查詢或者查詢不存在記錄來模拟間隙鎖:

語句1:SELECT * FROM table_name WHERE index  > 100 LOCK IN SHARE MODE (假設表中隻有index為1,2,…101的記錄, 那麼會鎖定101這條記錄的索引以及大于101的索引間隙)

語句2:UPDATE table_name SET … WHERE index = 103 (假設表中隻有index為1,2,…101,105,106的記錄, 那麼會鎖定101至105之間的索引間隙)

注意:間隙鎖沒有區分排他和共享模式,并且隻在UPDATE、DELETE以及特殊的SELECT語句中出現(INSERT語句采用了另外一種鎖——插入意向間隙鎖)

3)插入意向間隙鎖(Insert Intention Gap Lock)

在InnoDB中,INSERT語句會在插入記錄前,在記錄索引所在的間隙加上一個插入意向間隙鎖(Insert Intention Gap Lock),插入成功後會将插入意向間隙鎖轉換為對應記錄索引上的排它鎖。比如有3和5這兩條記錄,現要插入4這條記錄,那麼就會在索引區間(3,5)加Insert Intention Gap Lock,插入成功後會對4這條記錄的索引加X Lock。

由于插入意向間隙鎖是相容的,是以在多事務同時寫入不同資料至同一索引間隙(甚至是同一個非唯一索引)的時候,并不會發生鎖等待,進而提高并發插入性能。

4)後碼鎖( Next-Key  Lock) 

後碼鎖(Next-Key  Lock)就是行鎖(Record Lock)和間隙鎖(Gap Lock)的結合體,它鎖定記錄索引本身以及記錄索引的前後兩個間隙。在事務隔離級别為可重複讀且間隙鎖開啟的情況下, InnoDB會預設使用Next-Key  Lock ,示例如下:

SELECT * FROM table_name WHERE index  = 100 FOR UPDATE(假設表中存在index為95、100、105的記錄, 那麼會鎖定100這條記錄的索引以及(95,100)和(100,105)兩個索引間隙)

注意:當查詢的索引含有唯一屬性的時候,Next-Key Lock 會進行優化,将其降級為行鎖,即僅鎖住索引本身,而不是範圍。

4、“沖突-相容”規則2

考慮插入意向鎖後的沖突規則為:

Mysql之Innodb鎖模式和死鎖解析一. 背景知識二、鎖模式三、死鎖

三、死鎖

1、死鎖模型

死鎖一般是兩個或兩個以上事務互相等待對方釋放鎖,形成死循環所造成的,如下圖所示。

Mysql之Innodb鎖模式和死鎖解析一. 背景知識二、鎖模式三、死鎖

2、可能的幾種死鎖

1)多個事務按不同的順序鎖定相同的資料集導緻的死鎖

如果多個事務按不同的順序鎖定相同的資料集,此時事務之間就會形成循環等待造成死鎖,這是一種最常見也比較容易了解的死鎖。

2)索引不合理導緻的死鎖

由于InnoDB的鎖是加在索引上的,是以索引不合理将直接導緻鎖定範圍增大,發生鎖沖突和死鎖的的機率也随着增加。如果對索引和加鎖機制不太了解的話,就很難定位死鎖的原因。

3)插入意向間隙鎖與間隙所沖突導緻的死鎖 

 當多個事務持有間隙鎖,且事務互相插入對方鎖定的索引間隙時,便造成了死鎖。如圖所示。

Mysql之Innodb鎖模式和死鎖解析一. 背景知識二、鎖模式三、死鎖

4)唯一鍵值沖突導緻的死鎖

這個場景主要發生在三個或三個以上的事務同時進行唯一鍵值相同的記錄插入操作,如圖所示。

Mysql之Innodb鎖模式和死鎖解析一. 背景知識二、鎖模式三、死鎖

3、死鎖預防政策

1)減少事務操作的記錄數

2)合理設定索引(索引的粒度為一條記錄)

3)對事務中要操作的記錄進行排序

4)避免使用唯一鍵值限制

4、補充知識

1)檢視事務隔離級别

SELECT @@tx_isolation;

2)設定事務隔離級别

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

3)檢視鎖等待狀态

SELECT * FROM information_schema.innodb_locks;

4)檢視innodb狀态(包含最近的死鎖日志)

show engine innodb status;