什么是間隙鎖(gap lock)?
間隙鎖是一個在索引記錄之間的間隙上的鎖。
間隙鎖的作用?
保證某個間隙內的數據在鎖定情況下不會發生任何變化。比如我mysql默認隔離級別下的可重復讀(RR)。
當使用唯一索引來搜索唯一行的語句時,不需要間隙鎖定。如下面語句的id列有唯一索引,此時只會對id值為10的行使用記錄鎖。
select * from t where id = 10 for update;// 注意:普通查詢是快照讀,不需要加鎖
如果,上面語句中id列沒有建立索引或者是非唯一索引時,則語句會產生間隙鎖。
如果,搜索條件里有多個查詢條件(即使每個列都有唯一索引),也是會有間隙鎖的。
需要注意的是,當id列上沒有索引時,SQL會走聚簇索引的全表掃描進行過濾,由於過濾是在MySQL Server層面進行的。因此每條記錄(無論是否滿足條件)都會被加上X鎖。但是,為了效率考量,MySQL做了優化,對於不滿足條件的記錄,會在判斷后放 鎖,最終持有的,是滿足條件的記錄上的鎖。但是不滿足條件的記錄上的加鎖/放鎖動作是不會省略的。所以在沒有索引時,不滿足條件的數據行會有加鎖又放鎖的耗時過程。
間隙的范圍?
根據檢索條件向下尋找最靠近檢索條件的記錄值A作為左區間,向上尋找最靠近檢索條件的記錄值B作為右區間,即鎖定的間隙為(A,B] 左開右閉。
通過上面的描述,感覺很抽象?
沒關系,下面我們來通過具體的實驗來說明gap鎖。再次強調(當使用唯一索引來搜索唯一行的語句時,不需要間隙鎖定)
我們先創建一張數據表:
MariaDB [locktest]> create table gaplockt(
-> id int not null,
-> name varchar(255) not null primary key,
-> key `id_index` (`id`)
-> );
在向表中插入一些數據:
MariaDB [locktest]> insert into gaplockt values(1,'panchao'),(5,'songzuer'),(10,
'yangmi');
現在我們的表情況是這樣的:
MariaDB [locktest]> select * from gaplockt;
+----+----------+
| id | name |
+----+----------+
| 1 | panchao |
| 5 | songzuer |
| 10 | yangmi |
+----+----------+
接下來我們設置autocommit = 0;
MariaDB [locktest]> set autocommit = 0;
MariaDB [locktest]> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 0 |
+--------------+
現在我們的准備工作已經做完了,我們再事物A中給記錄添加一個寫鎖:
MariaDB [locktest]> select * from gaplockt where id = 5 for update;
+----+----------+
| id | name |
+----+----------+
| 5 | songzuer |
+----+----------+
由於id不是唯一索引,表上加上相應的gap鎖。如下圖:

以上語句會給表加上的gap鎖包括(gap2和gap3),也就是1~5U5~10,特別注意這個區間為左開右閉區間(1,10],沒理解沒關系,我們接下來通過實驗來驗證。
實驗1、
MariaDB [locktest]> insert into gaplockt values(6,'jingruyang');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(4,'jingruyang');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(1,'jingruyang');
Query OK, 1 row affected (0.00 sec)
MariaDB [locktest]> insert into gaplockt values(10,'jingruyang10');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
通過以上實驗,我們可以看到當id = 1的時候,我們是能添加數據的,但是當id = 10的時候我們是不能添加數據的,事實證明,我們的這個gap鎖區間為(1,10]
實驗2、
接下來我們來討論gap1和gap4的問題。
當我們執行以下語句的時候,或收獲gap4鎖。
MariaDB [locktest]> select * from gaplockt where id = 100 for update;
那么gap4鎖的范圍是多少呢? 是(10,100]嗎?
直接告訴你們答案,不是,它的范圍是(10,+∞),下面我們通過實驗來驗證。
MariaDB [locktest]> insert into gaplockt values(99,'jingruyang99');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(100,'jingruyang100');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(1000,'jingruyang1000');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
可以看到id=99 id= 100 id=1000 都是上了鎖的,所以gap4的區間為(10,+∞)
我們再來討論gap1
執行以下語句獲取gap1
MariaDB [locktest]> select * from gaplockt where id = -100 for update;
gap1的區間我直接告訴大家,(-∞,1)
下面我們通過實驗證明。
MariaDB [locktest]> insert into gaplockt values(-99,'jingruyang99');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(-100,'jingruyang99');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(-10000,'jingruyang99');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
看到上面的結果,就不用我多說什么了吧。
以下是兩個比較有參考價值的鏈接,有興趣的朋友可以看一下。
