InnoDB存儲引擎有3種行鎖的算法,其分別是:git
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);
複製代碼
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:優化
在上述的配置下,除了外鍵約束和惟一性檢查依然須要的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進行鎖定。
在默認的事務隔離級別下,即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存儲引擎默認的事務隔離級別是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:
我的博客: