搞懂 不可重複讀和幻讀

幻讀

  1. 因爲不少人(固然也包括本人), 容易搞混 不可重複讀幻讀, 這二者確實很是類似。html

    • 不可重複讀 主要是說屢次讀取一條記錄, 發現該記錄中某些列值被修改過。
    • 幻讀 主要是說屢次讀取一個範圍內的記錄(包括直接查詢全部記錄結果或者作聚合統計), 發現結果不一致(標準檔案通常指記錄增多, 記錄的減小應該也算是幻讀)。(能夠參考MySQL官方文檔對 Phantom Rows 的介紹)
  2. 其實對於 幻讀, MySQL的InnoDB引擎默認的RR級別已經經過MVCC自動幫咱們解決了, 因此該級別下, 你也模擬不出幻讀的場景; 退回到 RC 隔離級別的話, 你又容易把幻讀不可重複讀搞混淆, 因此這可能就是比較頭痛的點吧!
    具體能夠參考《高性能MySQL》對 RR 隔離級別的描述, 理論上RR級別是沒法解決幻讀的問題, 可是因爲InnoDB引擎的RR級別還使用了MVCC, 因此也就避免了幻讀的出現!

幻讀的延伸

MVCC雖然解決了幻讀問題, 但嚴格來講只是解決了部分幻讀問題, 接下來進行演示:mysql

1.打開客戶端1查看隔離級別及初始數據sql

mysql> SELECT @@SESSION.tx_isolation;
+------------------------+
| @@SESSION.tx_isolation |
+------------------------+
| REPEATABLE-READ        |
+------------------------+
1 row in set (0.00 sec)
 
mysql> select * from test_transaction;
+----+-----------+-----+--------+--------------------+
| id | user_name | age | gender | desctiption        |
+----+-----------+-----+--------+--------------------+
|  1 | 金剛狼 | 127 |      1 | 我有一雙鐵爪 |
|  2 | 鋼鐵俠 | 120 |      1 | 我有一身鐵甲 |
|  3 | 綠巨人 |   0 |      2 | 我有一身肉    |
+----+-----------+-----+--------+--------------------+
3 rows in set (0.00 sec)
 
mysql>

2.打開客戶端2查看隔離級別及初始數據數據庫

mysql> SELECT @@SESSION.tx_isolation;
+------------------------+
| @@SESSION.tx_isolation |
+------------------------+
| REPEATABLE-READ        |
+------------------------+
1 row in set (0.00 sec)
 
mysql> select * from test_transaction;
+----+-----------+-----+--------+--------------------+
| id | user_name | age | gender | desctiption        |
+----+-----------+-----+--------+--------------------+
|  1 | 金剛狼 | 127 |      1 | 我有一雙鐵爪 |
|  2 | 鋼鐵俠 | 120 |      1 | 我有一身鐵甲 |
|  3 | 綠巨人 |   0 |      2 | 我有一身肉    |
+----+-----------+-----+--------+--------------------+
3 rows in set (0.00 sec)
 
mysql>

3.在客戶端2中開啓事務, 而後查詢數據session

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from test_transaction;
+----+-----------+-----+--------+--------------------+
| id | user_name | age | gender | desctiption        |
+----+-----------+-----+--------+--------------------+
|  1 | 金剛狼 | 127 |      1 | 我有一雙鐵爪 |
|  2 | 鋼鐵俠 | 120 |      1 | 我有一身鐵甲 |
|  3 | 綠巨人 |   0 |      2 | 我有一身肉    |
+----+-----------+-----+--------+--------------------+
3 rows in set (0.00 sec)
 
mysql>

4.在客戶端1中插入一條id爲4的新數據 (直接自動提交)併發

mysql> insert into test_transaction (`id`,`user_name`,`age`,`gender`,`desctiption`) values (4, '死侍', 18, 0, 'A bad boy');
Query OK, 1 row affected (0.00 sec)
mysql> select * from test_transaction;
+----+-----------+-----+--------+--------------------+
| id | user_name | age | gender | desctiption        |
+----+-----------+-----+--------+--------------------+
|  1 | 金剛狼 | 127 |      1 | 我有一雙鐵爪 |
|  2 | 鋼鐵俠 | 120 |      1 | 我有一身鐵甲 |
|  3 | 綠巨人 |   0 |      2 | 我有一身肉    |
|  4 | 死侍    |  18 |      0 | A bad boy          |
+----+-----------+-----+--------+--------------------+
4 rows in set (0.00 sec)
 
mysql>

5.在客戶端2事務中再次查詢數據, 發現數據沒有變化(表示能夠重複讀, 而且克服了幻讀)!! 可是在客戶端2事務中插入一條id爲4的新數據, 發現提示數據已經存在!!!app

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from test_transaction;
+----+-----------+-----+--------+--------------------+
| id | user_name | age | gender | desctiption        |
+----+-----------+-----+--------+--------------------+
|  1 | 金剛狼 | 127 |      1 | 我有一雙鐵爪 |
|  2 | 鋼鐵俠 | 120 |      1 | 我有一身鐵甲 |
|  3 | 綠巨人 |   0 |      2 | 我有一身肉    |
+----+-----------+-----+--------+--------------------+
3 rows in set (0.00 sec)

mysql> select * from test_transaction;
+----+-----------+-----+--------+--------------------+
| id | user_name | age | gender | desctiption        |
+----+-----------+-----+--------+--------------------+
|  1 | 金剛狼 | 127 |      1 | 我有一雙鐵爪 |
|  2 | 鋼鐵俠 | 120 |      1 | 我有一身鐵甲 |
|  3 | 綠巨人 |   0 |      2 | 我有一身肉    |
+----+-----------+-----+--------+--------------------+
3 rows in set (0.00 sec)

mysql> insert into test_transaction (`id`,`user_name`,`age`,`gender`,`desctiption`) values (4, '死侍', 18, 0, 'A bad boy');
1062 - Duplicate entry '4' for key 'PRIMARY'
mysql> 

//而且, 此時`update/delete`也是能夠操做這條在事務中看不到的記錄的!

6.那麼這是什麼問題呢?性能

The snapshot of the database state applies to SELECT statements within a transaction, not necessarily to DML statements. If you insert or modify some rows and then commit that transaction, a DELETE or UPDATE statement issued from another concurrent REPEATABLE READ transaction could affect those just-committed rows, even though the session could not query them. If a transaction does update or delete rows committed by a different transaction, those changes do become visible to the current transaction.
我的認爲應該翻譯爲: 數據庫狀態的快照適用於事務中的SELECT語句, 而不必定適用於全部DML語句。 若是您插入或修改某些行, 而後提交該事務, 則從另外一個併發REPEATABLE READ事務發出的DELETE或UPDATE語句就可能會影響那些剛剛提交的行, 即便該事務沒法查詢它們。 若是事務更新或刪除由不一樣事務提交的行, 則這些更改對當前事務變得可見。

7.很多資料將MVCC併發控制中的讀操做能夠分紅兩類: 快照讀 (snapshot read)當前讀 (current read)翻譯

- 快照讀, 讀取專門的快照 (對於RC,快照(ReadView)會在每一個語句中建立。對於RR,快照是在事務啓動時建立的)
```
簡單的select操做便可(不須要加鎖,如: select ... lock in share mode, select ... for update)
```
針對的也是select操做

- 當前讀, 讀取最新版本的記錄, 沒有快照。 在InnoDB中,當前讀取根本不會建立任何快照。
```
select ... lock in share mode
select ... for update
```
針對以下操做, 會讓以下操做阻塞:    
```
insert
update
delete
```
- 在RR級別下, 快照讀是經過MVVC(多版本控制)和undo log來實現的, 當前讀是經過手動加record lock(記錄鎖)和gap lock(間隙鎖)來實現的。因此從上面的顯示來看,若是須要實時顯示數據,仍是須要經過加鎖來實現。這個時候會使用next-key技術來實現。

8.固然, 使用隔離性的最高隔離級別SERIALIZABLE也能夠解決幻讀, 但該隔離級別在實際中不多使用!版本控制

相關文章
相關標籤/搜索