一文講不清mysql裏的加鎖機制

首先你要知道:僅僅一篇文章是沒法全面講清楚明白mysql裏面的加鎖機制的!mysql

雖然網上有不少文章都號稱「理解mysql裏的加鎖機制看這一篇就夠了」,實際上都只是理論方面到位,工程實踐方面則都所不足。sql

本文針對工程實踐方面常常會碰到的一些關於鎖的問題進行剖析:設計

1.鎖的類型:code

常常碰到的主要是行鎖(record lock),間隙鎖(gap lock)和(next-key 鎖)。索引

  • 行鎖表示鎖住了某一行記錄
  • 間隙鎖表示鎖定了一個區間,在這個區間不能新增記錄。在一個區間不能新增記錄就避免了所謂幻讀的問題,叫區間鎖好像更好聽點,因此下面就叫區間鎖好了。注意:區間鎖不會對區間兩邊的記錄加鎖,只對區間內部加鎖。
  • next-key鎖能夠當作是以上二者的結合,表示既對記錄加了行鎖又在記錄的前面加上了區間鎖,實際上底層也是這樣實現的。

舉個例子:隊列

select * from t where id=100 for update
或者
update t set name=? where id=100

上面兩種類型的sql語句的加鎖方式是一致的:事務

  • 若是id爲惟一索引,100這條記錄存在,則會對這條記錄加上行鎖。
  • 若是id爲惟一或非惟一索引,100這條記錄不存在,則會加上區間鎖,具體的區間爲小於100的第一條記錄到大於100的第一條記錄之間的區間。
  • 若是id是非惟一索引,100這條記錄存在,則會加上next-key lock,既鎖定100這條記錄,又鎖定小於100的第一條記錄到100之間的區間。

另外:若是查詢條件是大於或小於某個值,也即查詢的結果預期是一個區間時也會加區間鎖。class

咱們這能夠這樣來分析:date

  1. 若是查詢條件命中了一個區間則會加上區間鎖
  2. 若是隻是精確命中了記錄則給記錄加上行鎖(前提條件:查詢條件爲惟一索引)
  3. 區間鎖是爲了防止加鎖期間其餘事務在區間新增記錄而設計的,因此,若是查詢條件是非惟一索引時且命中記錄時會加next-key鎖,它至關於包含了一個區間鎖:鎖定命中的記錄和記錄以前的區間。有了區間鎖,其餘事務就不能在區間內新增記錄從而產生幻讀了。

2.共享和排他鎖:select

共享鎖就不講了,平時經常使用的主要是排他鎖,即一個會話對某行記錄加了排他鎖,則其餘會話就不能再得到這把鎖。

可是在mysql裏面:區間鎖相互之間是不互斥的,也就是不一樣會話能夠同時給同一個區間加鎖!

這樣一來,就會產生如下幾種狀況:

  • 若是對方得到了行鎖,則你只能等它釋放。
  • 若是對方得到了區間鎖,則你也能夠獲取相同的區間鎖。
  • 若是對方得到了next-key鎖,則表示對方得到了行鎖和區間鎖,這時你不能獲取對方持有的行鎖,但能夠獲取對方持有的區間鎖。

因此,要不要等待對方的鎖,就看你當前申請的鎖是否包含對方持有的行鎖,若是有則須要等待,不然不須要。

3.死鎖問題

通常來講,若是全部事務都保持一致的加鎖順序,則不容易產生死鎖。可是不容易產生,不表明不會產生,在一些特殊的狀況下仍是有可能碰到的!

這是由於在mysql中有這樣一個奇葩的機制:

一個會話在申請鎖的時候若是須要等待另外一方釋放鎖,則這個申請會進入鎖申請隊列,這時若是另外一方接下來須要申請的鎖與申請隊列中的鎖有衝突,則會產生死鎖!

好比有這樣兩個會話:

會話1:

select * from t where id=100 for update //id是惟一索引且記錄存在,這裏產生行鎖

會話2:

select * from t where id=100 for update //id是惟一索引且記錄存在,由於會話1持有行鎖,會話2只能等待,該申請會進入鎖申請隊列。

這時若是會話1再執行:

update t set name=xxx where id>90

此處查詢條件的結果是一個區間,因而產生了區間鎖,這個鎖與申請隊列中的會話2申請的鎖有衝突,就產生的死鎖!

是否是很奇葩?會話2尚未持有任何鎖,僅僅是申請了鎖而已,就產生了死鎖。mysql大概是這樣判斷的:

  1. 會話1申請申請區間鎖。
  2. 由於隊列裏有其餘申請,因而先跟其餘申請作下比較,發現會話2已經先申請了id=100這條記錄的行鎖且處於區間中,因此它認爲會話1申請在後須要等待會話2。
  3. 這時爲避免死鎖再去檢查會話2申請的鎖是否被會話1持有
  4. 不檢查不知道,一檢查嚇一跳:會話1持有會話2須要的鎖,而在第二步時會話1當前又須要等待會話1!
  5. 結論:有死鎖,選擇犧牲會話2!
相關文章
相關標籤/搜索