1. 隔離級別
1. read uncommitted
事務可以看見其他未提交事務的修改。會導致臟讀。
2. read committed
事務只能看見其他提交事務的修改。但是如果事務A讀取一批數據set,其他事務之后修改了這個數據set并提交(此時事務A沒有提交),這時事務A再讀取數據set就跟第一次讀取的結果不一致。會導致不可重復讀。
3. repeatable read
在事務執行過程中,重復讀到的數據是一致的
4. serializable
2. 問題
1. 臟讀
事務A讀到了事務B未提交的數據。(read uncommitted)
2. 不可重復讀
事務A第一次讀取行num,此時事務B修改行num并提交,事務A再讀行num,數據會發生變化。(read uncommitted, read committed)
3. 幻讀
事務A第一次查詢范圍query_range,返回n行,此時事務B在該查詢范圍內插入了一行數據并提交,事務A再次查詢范圍query_range會看到B插入的數據。(read uncommitted, read committed, repeatable read)
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.
在同一個事務執行過程中,兩次相同的查詢,但是查詢的返回數據行數不同。
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
---|---|---|---|
read uncommitted | yes | yes | yes |
read committed | no | yes | yes |
repeatable read | no | no | yes |
serializable | no | no | no |
3. Innodb 行鎖算法
1. record lock 鎖住某一行
2. gap lock 鎖住兩行之間的間隙(不包括行本身)
3. next key lock 同時應用1,2
4. 實例
0. current read, snapshot read
1. 實例1
- 隔離級別 repeatable read
- 數據庫 mysql innodb
CREATE TABLE `test_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
mysql> select * from test_lock;
+----+------+
| id | a |
+----+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
+----+------+
6 rows in set (0.00 sec)
開啟sessionA, sessionB
sessionA> begin;
Query OK, 0 rows affected (0.01 sec)
sessionA> delete from test_lock where a=3;
Query OK, 1 row affected (0.00 sec)
sessionA>
此時鎖(行鎖、gap鎖)的情況如下圖,標紅為加鎖,采用next key lock。(不光在索引a上加鎖,索引a中3所對應的主鍵索引也會加鎖,只畫了索引a)
注意:這里是主鍵索引的順序與a索引的順序一致的情況。一致的情況下,新插入的4會插入在原來的4之后。我們定義函數index_key(x), 表示獲取x所對應的主鍵索引,new(x)表示新插入的x,old(x)表示已經存在的x。如果index_key(old(4))>index_key(new(4)),那新的4是插不進去的,因為新的4會被放在老的4的前面。同理,新插入的2也有可能插入進去(只要index_key(new(2))<index_key(old(2)))。具體的例子整理后發出
對于sessionB,插入2,3失敗,插入4成功。(如圖lock.png,新插入的2會被2與3之間gap鎖阻止,新插入的3肯定失敗,但是新插入的4就沒問題)
sessionB> insert into test_lock(a) values(2);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
sessionB> insert into test_lock(a) values(3);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
sessionB> insert into test_lock(a) values(4);
Query OK, 1 row affected (0.00 sec)
2. 實例2
- 隔離級別 repeatable read
- 數據庫 mysql innodb
驗證實例1的另一種情況。
CREATE TABLE `test_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
mysql> select * from test_lock2;
+----+------+
| id | a |
+----+------+
| 1 | 1 |
| 5 | 3 |
| 3 | 5 |
| 7 | 7 |
+----+------+
6 rows in set (0.00 sec)
開啟sessionA:
sessionA> begin;
sessionA> delete from test_lock2 where a=5;
這個時候情況如下圖所示:
如果按照實例1的分析,不看主鍵索引id,應該插入(id=4,a=3)應該會被gap鎖鎖定,但是事實是可以插入成功。因為插入的(i4=d,a=3)放在了(id=5,a=3)的上面。
同樣的道理,不看主鍵索引id,通過實例1的分析,插入(id=6,a=7)應該可以插入,但是事實不行,因為(id=6,a=7)被a=5與a=7之間的gap鎖阻止了。
新插入的(id=4, a=3),需要判斷會插入在(id=5, a=3)之前還是之后,很明顯之前(4<5),那么(id=3, a=5)與(id=5, a=3)之間的gap鎖不會阻止。
新插入的(id=6, a=7),需要判斷會插入在(id=7, a=7)之前還是之后,很明顯之前(6<7),那么(id=6, a=7)會被(id=3, a=5)與(id=5, a=3)與(id=7, a=7)之間的gap鎖阻止。