MySQL InnoDB支持三種行鎖定方式:
行鎖(Record Lock):鎖直接加在索引記錄上面。間隙鎖(Gap Lock):鎖加在不存在的空閒空間,可以是兩個索引記錄之間,也可能是第一個索引記錄之前或最後一個索引之後的空間。Next-Key Lock:行鎖與間隙鎖組合起來用就叫做Next-Key Lock。默認情況下,InnoDB工作在可重復讀隔離級別下,並且以Next-Key Lock的方式對數據行進行加鎖,這樣可以有效防止幻讀的發生。Next-Key Lock是行鎖與間隙鎖的組合,這樣,當InnoDB掃描索引記錄的時候,會首先對選中的索引記錄加上行鎖(Record Lock),再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。如果一個間隙被事務T1加了鎖,其它事務是不能在這個間隙插入記錄的。
我們來看看例子,首先建一張表。
CREATE TABLE tb1 ( id int(11) NOT NULL, id2 int(11) NOT NULL, PRIMARY KEY (id), KEY idx (id2) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
創建一些記錄。
insert into tb1 values(1, 3), (2, 6), (3, 9);
tb1表現在有3條記錄,其中普通索引字段id2的值3、6、9把間隙分成了四份:(-,3)、(3、6)、(6、9)、(9、+)。現在我們看看基於id2 = 6加鎖的情況,會話S1中對id2 = 6的記錄加S鎖。
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from tb1 where id2 = 6 lock in share mode; +----+-----+ | id | id2 | +----+-----+ | 2 | 6 | +----+-----+ 1 row in set (0.00 sec) mysql>
會話S2中嘗試插入id2 = 5或id2 = 7的記錄。
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> insert into tb1 values(4, 5); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tb1 values(4, 7); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql>
從結果可知,會話2發生了鎖等待超時,因為會話S1中的事務鎖住了這些空隙(3、6)與(6、9)。如果插入的記錄是id2 = 1或id2 = 10,那就不會有問題,因為這些間隙沒有被任何事務鎖住,如:
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> insert into tb1 values(4, 5); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tb1 values(4, 7); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tb1 values(4, 2); Query OK, 1 row affected (0.00 sec) mysql> insert into tb1 values(5, 10); Query OK, 1 row affected (0.00 sec) mysql>
間隙鎖在InnoDB的唯一作用就是防止其它事務的插入操作,以此來達到防止幻讀的發生,所以間隙鎖不分什麼共享鎖與排它鎖。另外,在上面的例子中,我們選擇的是一個普通(非唯一)索引字段來測試的,這不是隨便選的,因為如果InnoDB掃描的是一個主鍵、或是一個唯一索引的話,那InnoDB只會采用行鎖方式來加鎖,而不會使用Next-Key Lock的方式,也就是說不會對索引之間的間隙加鎖,仔細想想的話,這個並不難理解,大家也可以自己測試一下。
要禁止間隙鎖的話,可以把隔離級別降為讀已提交,或者開啟參數innodb_locks_unsafe_for_binlog。