MySQL InnoDB存儲引擎:行鎖的3種算法

行鎖的三種算法

InnoDB存儲引擎有3種行鎖的算法,其分別是:git

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

Record Lock老是會鎖住索引記錄,若是InnoDB存儲引擎創建的時候沒有設置任何一個索引,這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。github

Next-Key Lock是結合了Gap Lock和Record Lock的一種鎖定算法,在Next-Key Lock算法下,innodb對於行的查詢都是採用這種鎖定算法。例如一個索引有9,11,13,20這4個值,那麼該索引可能被Next-Key Locking的範圍爲(左開右閉 ): (- &,9] (9,11] (13,20] (20,+ &)算法

採用Next-Key Lock的鎖定技術稱爲Next-Key Locking。這種設計的目的是爲了解決幻讀(Phantom Problem)。利用這種鎖定技術,鎖定的不是單個值,而是一個範圍。數據庫

當查詢的索引含有惟一屬性時,innodb存儲引擎會對Next-Key Lock進行優化,將其降級爲Record Lock,即鎖住索引記錄自己,而再也不是範圍。 對於惟一索引,其加上的是Record Lock,僅鎖住記錄自己。但也有特別狀況,那就是惟一索引由多個列組成,而查詢僅是查找多個惟一索引列中的其中一個,那麼加鎖的狀況依然是Next-key lock。bash

DROP TABLE
IF EXISTS t;

CREATE TABLE t (a INT PRIMARY KEY);

INSERT INTO t
VALUES
	(1),
	(2),
	(5);
複製代碼

表t中共有一、二、5三個值。在上面的例子中,在會話A中首先對a=5進行X鎖定。而因爲a是主鍵且惟一,所以鎖定的僅是5這個值,而不是(2,5)這個範圍,這樣在會話B中插入值4而不會阻塞,能夠當即插入並返回。即鎖定由Next-Key Lock算法降級爲了Record Lock,從而提升應用的併發性。正如前面所介紹的, Next-Key降級爲Record Lock僅在查詢的列是惟一索引的狀況下。如果輔助索引,則狀況會徹底不一樣。一樣,首先根據以下代碼建立測試表z:

CREATE TABLE Z (
	a INT,
	b INT,
	PRIMARY KEY (a),
	KEY (b)
);

INSERT INTO Z
VALUES
	(1, 1),
	(3, 1),
	(5, 3),
	(7, 6),
	(10, 8);
複製代碼

表z的列b是輔助索引,若在會話A中執行下面的SQL語句:微信

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

很明顯,這時SQL語句經過索引列b進行查詢,所以其使用傳統的Next-Key Locking技術加鎖,而且因爲有兩個索引,其須要分別進行鎖定對於彙集索引,其僅對列a等於5的索引加上Record Lock。而對於輔助索引,其加上的是Next-Key Locking,鎖定的範圍是(1,3),特別須要注意的是,InnoDB存儲引擎會對輔助索引下一個鍵值加上gap lock,即還有一個輔助索引範圍爲(3,6)的鎖。 所以,若在新會話B中運行下面的SQL語句,都會被阻塞:併發

SELECT * FROM Z WHERE a=5 LOCK IN SHARE MODE;
INSERT INTO Z SELECT 4,2;
INSERT INTO Z SELECT 6,5;
複製代碼

第一個SQL語句不能執行,由於在會話A中執行的SQL語句已經彙集索引中列a=5的值加上X鎖,所以執行會被阻塞。第二個SQL語句,主鍵插入4,沒有問題,可是插入的輔助索引值2在鎖定的範圍(1,3)中所以執行一樣會被阻塞。第三個SQL語句,插入的主鍵6沒有被鎖定,5也不在範圍(1,3)之間。但插入的值5在另外一個鎖定範圍(3,6)中,故一樣須要等待。而下面的SQL語句,不會被阻塞,能夠當即執行:性能

INSERT INTO Z SELECT 8,6;
INSERT INTO Z SEELCT 2,0;
INSERT INTO Z SELECT 6,7;
複製代碼

從上面的例子中能夠看到,Gap Lock的做用是爲了阻止多個事務將記錄插入到同一個範圍內,而這會致使Phantom Problem問題的產生。 例如在上面的例子中,會話A中用戶已經鎖定了b=3的記錄。若此時沒有Gap Lock鎖定(3,6),那麼用戶能夠插入索引b列爲3的記錄,這會致使會話A中的用戶再次執行一樣查詢時會返回不一樣的記錄,致使Phantom Problem問題的產生。測試

用戶能夠經過如下兩種方式來顯式地關閉Gap Lock:優化

  • 將事務的隔離級別設置爲READ COMMITTED
  • 將參數innodb_locks_unsafe_for_binlog設置爲1

在上述的配置下,除了外鍵約束和惟一性檢查依然須要的Gap Lock,其他狀況僅使用Record Lock進行鎖定。但須要牢記的是,上述設置破壞了事務的隔離性,而且對於replication,可能會致使主從數據的不一致。此外,從性能上來看,READ COMMITTED也不會優於默認的事務隔離級別READ REPEATABLE。

在InnoDB存儲引擎中,對於INSERT的操做,其會檢查插入記錄的下一條記錄是否被鎖定,若已被鎖定,則不容許查詢。對於上面的例子,會話A已經鎖定了表z中b=3的記錄,即已經鎖定了(1,3)的範圍,這時若在其餘會話中進行以下的插入一樣會致使阻塞:

INSERT INTO Z SELECT 2,2;
複製代碼

由於在輔助索引列b上插入值爲2的記錄時,會監測到下一個記錄3已經被索引。而將插入修改成以下的值,能夠當即執行:

INSERT INTO Z SELECT 2,0;
複製代碼

最後再次提醒的是,對於惟一鍵值的鎖定,Next-Key Lock降級爲Record Lock僅存在於查詢全部的惟一索引一列。若惟一索引由多個列組成,而查詢是查找多個惟一索引列中的其中一個,那麼查詢實際上是range類型查詢,而不是point類型查詢故InnoDB存儲引擎依然使用Next-Key Lock進行鎖定。

解決 Phantom Problem

在默認的事務隔離級別下,即REPEATABLE READ下,InnoDB存儲引擎採用 Next-Key Locking機制來避免Phantom Problem (幻像問題)。這點可能不一樣於與其餘的數據庫,如Oracle數據庫,由於其可能須要在SERIALIZABLE的事務隔離級別下才能解決 Phantom Problem。

Phantom Problem是指在同一事務下,連續執行兩次一樣的SQL語句可能致使不一樣的結果,第二次的SQL語句可能會返回以前不存在的行。

下面將演示這個例子,使用前一小節所建立的表t。表t由一、二、5這三個值組成:

DROP TABLE
IF EXISTS t;

CREATE TABLE t (a INT PRIMARY KEY);

INSERT INTO t
VALUES
	(1),
	(2),
	(5);
複製代碼

若這時事務T1執行以下的SQL語句:

SELECT * FROM t WHERE a> 2 FOR UPDATE;
複製代碼

注意這時事務T1並無進行提交操做,上述應該返回5這個結果。若與此同時,另外一個事務T2插入了 4這個值,而且數據庫容許該操做,那麼事務T1再次執行上述SQL語句會獲得結果4和5。這與第一次獲得的結果不一樣,違反了事務的隔離性,即當前事務可以看到其餘事務的結果。其過程如表6-13所示:

InnoDB存儲引擎採用Next-Key Locking的算法避免Phantom Problem。對於上述的SQL語句SELECT * FROM t WHERE a>2 FOR UPDATE,其鎖住的不是5這單個值,而是對(2, +〇〇)這個範圍加了 X鎖。所以任何對於這個範圍的插入都是不被容許的,從而避免 Phantom Problem。

InnoDB存儲引擎默認的事務隔離級別是REPEATABLE READ,在該隔離級別下, 其採用Next-Key Locking的方式來加鎖。而在事務隔離級別READ COMMITTED下,其僅採用Record Lock,所以在上述的示例中,會話A須要將事務的隔離級別設置爲READ COMMITTED。

此外,用戶能夠經過InnoDB存儲引擎的Next-Key Locking機制在應用層面實現惟一性的檢查。 例如:

SELECT * FROM table WHERE col=xxx LOCK IN SHARE MODE;
If not found any row:
# unique for insert value
INSERT INTO table VALUES (...);
複製代碼

若是用戶經過索引査詢一個值,並對該行加上一個SLock,那麼即便査詢的值不在,其鎖定的也是一個範圍,所以若沒有返回任何行,那麼新插人的值必定是惟一的。也許有讀者會有疑問,若是在進行第一步SELECT •••LOCK IN SHARE MODE操做時,有多個事務併發操做,那麼這種惟一性檢査機制是否存在問題。其實並不會,由於這時會致使死鎖,只有一個事務的插人操做會成功,而其他的事務會拋出死鎖的錯誤,如表6-14所示。

本文整理自:《MySQL技術內幕:InnoDB存儲引擎》 第二版

我的微信公衆號:

我的github:

github.com/jiankunking

我的博客:

jiankunking.com

相關文章
相關標籤/搜索