事務隔離級別-REPEATABLE-READ && 間隙鎖mysql
表結構sql
create table t( name varchar(255) primary key, id int not null, key idx_id (id) ); insert into t(name,id) values ('a',15), ('b',10),('c',6),('d',10),('f',11),('zz',2);
Session Aspa
mysql> begin; Query OK, 0 rows affected (0.00 sec)
Session Brest
mysql> begin; Query OK, 0 rows affected (0.00 sec)
Session Acode
mysql> select * from t; +------+----+ | name | id | +------+----+ | zz | 2 | | c | 6 | | b | 10 | | d | 10 | | f | 11 | | a | 15 | +------+----+ 6 rows in set (0.00 sec) mysql> select * from t where id = 10 for update; +------+----+ | name | id | +------+----+ | b | 10 | | d | 10 | +------+----+ 2 rows in set (0.00 sec)
咱們在Session A裏執行當前讀的select...for update操做,那這條sql語句時如何加鎖的呢?看下圖,orm
這幅圖中有一個GAP鎖,並且GAP鎖看起來也不是加在記錄上的,倒像是加載兩條記錄之間的位置,GAP鎖有何用?索引
其實這個多出來的GAP鎖,就是RR隔離級別,相對於RC隔離級別,不會出現幻讀的關鍵。確實,GAP鎖鎖住的位置,也不是記錄自己,而是兩條記錄之間的GAP。所謂幻讀,就是同一個事務,連續作兩次當前讀 (例如:select * from t1 where id = 10 for update;),那麼這兩次當前讀返回的是徹底相同的記錄 (記錄數量一致,記錄自己也一致),第二次的當前讀,不會比第一次返回更多的記錄 (幻象)。事務
如何保證兩次當前讀返回一致的記錄,那就須要在第一次當前讀與第二次當前讀之間,其餘的事務不會插入新的知足條件的記錄並提交。爲了實現這個功能,GAP鎖應運而生。it
如圖中所示,有哪些位置能夠插入新的知足條件的項 (id = 10),考慮到B+樹索引的有序性,知足條件的項必定是連續存放的。記錄[6,c]以前,不會插入id=10的記錄;[6,c]與[10,b]間能夠插入[10, aa];[10,b]與[10,d]間,能夠插入新的[10,bb],[10,c]等;[10,d]與[11,f]間能夠插入知足條件的[10,e],[10,z]等;而[11,f]以後也不會插入知足條件的記錄。所以,爲了保證[6,c]與[10,b]間,[10,b]與[10,d]間,[10,d]與[11,f]不會插入新的知足條件的記錄,MySQL選擇了用GAP鎖,將這三個GAP給鎖起來。io
Insert操做,如insert [10,aa],首先會定位到[6,c]與[10,b]間,而後在插入前,會檢查這個GAP是否已經被鎖上,若是被鎖上,則Insert不能插入記錄。所以,經過第一遍的當前讀,不只將知足條件的記錄鎖上 (X鎖),同時仍是增長3把GAP鎖,將可能插入知足條件記錄的3個GAP給鎖上,保證後續的Insert不能插入新的id=10的記錄,也就杜絕了同一事務的第二次當前讀,出現幻象的狀況。
而後咱們在Session B中作相應的Insert操做,驗證一下上面的說法。
Session B
執行如下插入操做
mysql> select * from t; +------+----+ | name | id | +------+----+ | zz | 2 | | c | 6 | | b | 10 | | d | 10 | | f | 11 | | a | 15 | +------+----+ 6 rows in set (0.00 sec) mysql> insert t(name,id) values ('aa',10); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert t(name,id) values ('bb',10); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert t(name,id) values ('e',11); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql>
能夠看到咱們執行了屢次插入,都失敗了,就是由於GAP上加了間隙鎖的緣由,致使插入不成功,也就防止了Session A第二次當前讀的時候不會出現幻讀。
當執行這條sql時insert t(name,id) values ('bb',10)時,相應的鎖的信息;
mysql> use information_schema Database changed mysql> select * from INNODB_LOCKS; +--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+ | lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data | +--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+ | 11662:65:4:5 | 11662 | X,GAP | RECORD | `test`.`t` | idx_id | 65 | 4 | 5 | 10, 'd' | | 11661:65:4:5 | 11661 | X | RECORD | `test`.`t` | idx_id | 65 | 4 | 5 | 10, 'd' | +--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+ 2 rows in set (0.00 sec)
===========END===========