1.概述
當我們用范圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件范圍內但並不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖)。
2.InnoDB存儲引擎的間隙鎖阻塞例子
先創建一個間隙臨時表,ID為主鍵自增:
MySQL [(none)]> CREATE TABLE goods. tab_gap (ID INT NOT NULL auto_increment,Name VARCHAR(50),PRIMARY KEY(ID)); Query OK, 0 rows affected (0.02 sec)
先插入五行數據:
MySQL [(none)]> INSERT INTO goods.tab_gap (ID,`Name`) VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e'); Query OK, 5 rows affected (0.01 sec) Records: 5 Duplicates: 0 Warnings: 0
示例:
session_1 |
session_2 |
(1)查詢事務隔離級別是否是可重復讀 |
(1)查詢事務隔離級別是否是可重復讀 |
MySQL [(none)]> SHOW VARIABLES LIKE 'transaction_isolation%'; +-----------------------+-----------------+ | Variable_name | Value | +-----------------------+-----------------+ | transaction_isolation | REPEATABLE-READ | +-----------------------+-----------------+ 1 row in set (0.01 sec) |
MySQL [(none)]> SHOW VARIABLES LIKE 'transaction_isolation%'; +-----------------------+-----------------+ | Variable_name | Value | +-----------------------+-----------------+ | transaction_isolation | REPEATABLE-READ | +-----------------------+-----------------+ 1 row in set (0.01 sec) |
(2)先設置事務T1提交類型為事務非自動提交。 |
(2)先設置事務T2提交類型為事務非自動提交。 |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
MySQL [(none)]> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) |
(3)為當前session_1事務中Name=’f’不存在的記錄行加排他鎖。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_gap WHERE `Name`='f' FOR UPDATE; Empty set (0.00 sec) |
|
|
(3)如果這時session_2插入ID=6的記錄行(注意:這條記錄並不存在),也會出現鎖等待。 |
MySQL [(none)]> INSERT INTO goods.tab_gap (ID,`Name`) VALUES (6,'f'); 阻塞... |
|
(4)回滾事務。 |
|
MySQL [(none)]> ROLLBACK; Query OK, 0 rows affected (0.00 sec) |
|
|
(4)由於session_1回滾后釋放了間隙鎖, 所以當前session_2可以獲得鎖並成功插入記錄。 |
MySQL [(none)]> INSERT INTO goods.tab_gap (ID,`Name`) VALUES (6,'f'); Query OK, 1 row affected (38.41 sec) |
|
|
(5)提交事務,釋放鎖。 |
MySQL [(none)]> COMMIT; Query OK, 0 rows affected (0.00 sec) |
|
(4)為當前session_1事務中ID>4范圍的記錄行加排他鎖。 |
|
MySQL [(none)]> SELECT * FROM goods.tab_gap WHERE ID>4 FOR UPDATE; +----+------+ | ID | Name | +----+------+ | 5 | e | | 6 | f | +----+------+ 2 rows in set (0.00 sec) |
|
|
(6)為當前session_2事務中ID=5的記錄行加排他鎖,發生阻塞。 |
MySQL [(none)]> SELECT * FROM goods.tab_gap WHERE ID>5 FOR UPDATE; 阻塞... |
從示例中可以看到,在Mysql默認事務隔離級別下,如果在相等的條件中給一個不存在的記錄行加鎖,InnoDB也會使用間隙鎖,不然session_1會出現幻讀(session_1事務中能查詢到session_2插入ID=6的記錄行)。而當我們在ID>4范圍條件內加鎖,InnoDB不僅會對符合條件的ID值為5、6的記錄行加鎖,也會為大於6以上的記錄行加間隙鎖(不管數據是否存在)。
3.總結
InnoDB使用間隙鎖的目的,一方面是為了防止幻讀,以滿足相關隔離級別的要求。另外一方面,是為了滿足其恢復和復制的需要。有關其恢復和復制對鎖機制的影響,以及不同隔離級別下InnoDB使用間隙鎖的情況,在后續的章節中會做進一步介紹。很顯然,在使用范圍條件檢索並鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件范圍內鍵值的並發插入,這往往會造成嚴重的鎖等待!因此,在實際應用開發中,尤其是並發插入比較多的應用程序,我們要盡量優化業務邏輯,盡量使用相等條件來訪問更新數據,避免使用范圍條件。還要特別說明的是,InnoDB除了通過范圍條件加鎖時使用間隙鎖外,如果使用相等條件請求給一個不存在的記錄加鎖,InnoDB也會使用間隙鎖!
參考文獻:
深入淺出MySQL大全