MySQL探祕(七):InnoDB行鎖算法

 在上一篇《InnoDB一致性非鎖定讀》中,咱們瞭解到InnoDB使用一致性非鎖定讀來避免在通常的查詢操做(SELECT FOR UPDATE等除外)時使用鎖。然而鎖這個事情是沒法避免的,數據的寫入,修改和刪除都須要加鎖。今天咱們就繼續學習InnoDB鎖相關的知識。html

 因爲文章涉及的概念比較多,懼怕你們看完後會罵人,有一種字我都認識,就不太懂的感受,文章會給出一些實例和試驗,依據具體案例來說解這些概念。畢竟,實踐才能出真知。mysql

 InnoDB存儲引擎支持表鎖和行鎖。顧名思義,表鎖是鎖住整張表,行鎖只是鎖住某些行。InnoDB經過給索引項加鎖來實現行鎖,若是沒有索引,則經過隱藏的聚簇索引來對記錄加鎖。若是操做不經過索引條件檢索數據,InnoDB 則對錶中的全部記錄加鎖,實際效果就和表鎖同樣。InnoDB存儲引擎有3種行鎖的算法,分別是:算法

  • Record Lock: 單個記錄上的鎖
  • Gap Lock: 間隙鎖,鎖定一個範圍,但不包括記錄本上
  • Next-Key Lock: Gap Lock+Record Lock,鎖定一個範圍,而且鎖定記錄自己

 以下圖所示,sql

三種鎖算法

 例如一個索引有10,11,13,20這四個值。InnoDB能夠根據須要使用Record Lock將10,11,13,20四個索引鎖住,也可使用Gap Lock將(-∞,10),(10,11),(11,13),(13,20),(20, +∞)五個範圍區間鎖住。Next-Key Locking相似於上述兩種鎖的結合,它能夠鎖住的區間有爲(-∞,10],(10,11],(11,13],(13,20],(20, +∞),能夠看出它即鎖定了一個範圍,也會鎖定記錄自己。數據庫

 InnoDB存儲引擎的鎖算法的一些規則以下所示,後續章節會給出對應的實驗案例和詳細講解。bash

  • 在不經過索引條件查詢時,InnoDB 會鎖定表中的全部記錄。因此,若是考慮性能,WHERE語句中的條件查詢的字段都應該加上索引。服務器

  • InnoDB經過索引來實現行鎖,而不是經過鎖住記錄。所以,當操做的兩條不一樣記錄擁有相同的索引時,也會由於行鎖被鎖而發生等待。性能

  • 因爲InnoDB的索引機制,數據庫操做使用了主鍵索引,InnoDB會鎖住主鍵索引;使用非主鍵索引時,InnoDB會先鎖住非主鍵索引,再鎖定主鍵索引。學習

  • 當查詢的索引是惟一索引(不存在兩個數據行具備徹底相同的鍵值)時,InnoDB存儲引擎會將Next-Key Lock降級爲Record Lock,即只鎖住索引自己,而不是範圍。優化

  • InnoDB對於輔助索引有特殊的處理,不只會鎖住輔助索引值所在的範圍,還會將其下一鍵值加上Gap LOCK。

  • InnoDB使用Next-Key Lock機制來避免Phantom Problem(幻讀問題)。

真的瞭解本質嗎?

 在不經過索引條件查詢時,InnoDB 會鎖定表中的全部記錄。你們能夠登陸上本身的MySQL服務器,親自試驗一下。

示例一

 試驗發現,會話二的查詢操做真的是會發生等待。那麼,這句話真的是對的嗎?咱們可使用《InnoDB鎖的類型和狀態查詢》中查詢數據鎖的方法查詢一下,注意必須在會話二操做還在等待時進行查詢,不然查詢不到

查詢鎖信息

 其中lock_trx_id爲1851的事務是會話二的事務,另外一個是會話一的事務。咱們能夠看到兩個鎖都要對值爲1的主鍵索引加鎖。須要注意的是,這裏是對主鍵進行加鎖。兩者之間的關係是怎麼肯定的呢?咱們能夠經過information_schema.INNODB_LOCK_WAITS中的數據肯定。

 奇怪,不是說好的鎖定表中的全部記錄嘛?查找了不少資料,發現INNODB_LOCKS的定義以下:

The INNODB_LOCKS table contains information about each lock that an InnoDB transaction has requested but not yet acquired, and each lock that a transaction holds that is blocking another transaction.

 也就是說,這張表並不會顯示全部鎖的信息,而是隻顯示要申請卻沒有申請到,和已經持有鎖而且阻塞其餘線程的鎖信息。怪不得必須在會話二進行等待時進行查詢才能查獲得數據。

 由於兩個會話的操做都要鎖住全部的行,因此發現每次在第一行記錄上就發生了鎖等待。那咱們使用插入語句試試。表e1的主鍵a的值爲1-4,咱們分別插入主鍵爲1-4(固然會有主鍵重複問題,可是因爲有鎖,一直等待)的新記錄,分別查詢鎖信息,就能看到會話一的事務對全部的主鍵都加了鎖,也就是對全部的記錄都加了鎖。

是索引,而不是記錄

 InnoDB存儲引擎的行鎖是經過鎖住索引實現的,而不是記錄。這是理解不少數據庫鎖問題的關鍵。

 因爲InnoDB特殊的索引機制,數據庫操做使用主鍵索引時,InnoDB會鎖住主鍵索引;使用非主鍵索引時,InnoDB會先鎖住非主鍵索引,再鎖定主鍵索引。不瞭解InnoDB索引機制的能夠參考這篇文章

 以下圖所示,當InnoDB鎖定非主鍵索引b時,它也會鎖住其對應的主鍵索引,因此鎖住b值爲2和3的非主鍵索引,那麼與其相關的a值爲6,5的主鍵索引也須要被鎖住。

非主鍵索引的加鎖

 好比說,一種常見的死鎖狀況通常出如今以下圖所示的操做場景中。

示例2

 會話一的語句使用了b上的索引,由於它是非主鍵索引,因此會先在b索引上添加鎖,再去a索引上加鎖。而會話二的語句偏偏相反,會先在索引a上加鎖,再去索引b加鎖。這種狀況下,就可能出現死鎖。

Next-Key Lock鎖到底有什麼用?

 默認隔離級別REPEATABLE-READ下,InnoDB中行鎖默認使用算法Next-Key Lock,只有當查詢的索引是惟一索引或主鍵時,InnoDB會對Next-Key Lock進行優化,將其降級爲Record Lock,即僅鎖住索引自己,而不是範圍。當查詢的索引爲輔助索引時,InnoDB則會使用Next-Key Lock進行加鎖。InnoDB對於輔助索引有特殊的處理,不只會鎖住輔助索引值所在的範圍,還會將其下一鍵值加上Gap LOCK。

 廢話很少說,咱們來看一下相關的實驗,先作一下準備。

CREATE TABLE e4 (a INT, b INT, PRIMARY KEY(a), KEY(b));
INSERT INTO e4 SELECT 1,1;
INSERT INTO e4 SELECT 3,1;
INSERT INTO e4 SELECT 5,3;
INSERT INTO e4 SELECT 7,6;
INSERT INTO e4 SELECT 10,8;
複製代碼

 而後開啓一個會話執行下面的語句。

SELECT * FROM e4 WHERE b=3 FOR UPDATE; 
複製代碼

 由於經過索引b來進行查詢,因此InnoDB會使用Next-Key Lock進行加鎖,而且索引b是非主鍵索引,因此還會對主鍵索引a進行加鎖。對於主鍵索引a,僅僅對值爲5的索引加上Record Lock(由於以前的規則)。而對於索引b,須要加上Next-Key Lock索引,鎖定的範圍是(1,3]。除此以外,還會對其下一個鍵值加上Gap Lock,即還有一個範圍爲(3,6)的鎖。  你們能夠再新開一個會話,執行下面的SQL語句,會發現都會被阻塞。

SELECT * FROM e4 WHERE a = 5 FOR UPDATE;  # 主鍵a被鎖
INSERT INTO e4 SELECT 4,2;   # 插入行b的值爲2,在鎖定的(1,3]範圍內
INSERT INTO e4 SELECT 6,5; # 插入行b的值爲5,在鎖定的(3,6)範圍內
複製代碼

 InnoDB引擎採用Next-Key Lock來解決幻讀問題。由於Next-Key Lock是鎖住一個範圍,因此就不會產生幻讀問題。可是須要注意的是,InnoDB只在Repeatable Read隔離級別下使用該機制。

後記

 咱們後續還會探討InnoDB的事務的知識,請你們持續關注。

參考

相關文章
相關標籤/搜索