事務隔離級別-REPEATABLE-READ && 間隙鎖

事務隔離級別-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===========

相關文章
相關標籤/搜索