如何真正來區分一下 不可重復讀和幻讀
幻讀 (間隙鎖)
1.由于很多人(當然也包括本人), 容易搞混 不可重復讀 和 幻讀 , 這兩者確實非常相似。
- 但 不可重復讀 的重點是針對 update, delete。
- 而 幻讀 的重點針對的是 insert。(可以參考MySQL官方文檔對 Phantom Rows 的介紹)
2.雖然網上有不少資料提到幻讀, 但是可能表達的都不太準確, 比如這樣一段對幻讀的解釋 '同樣的條件, ***次和第二次讀出來的記錄不一樣' 在網絡上隨處可見, 但其實并不準確, 因為 delete 其實并不是幻讀的范疇(MySQL官方文檔對 Phantom Rows 的介紹)也一點都沒涉及到delete)。
3.如果手動加鎖來演示, 你便會看清他們的本質:
- 如果 insert, 則操作被阻塞, 并且可以看到具體加的是X鎖+GAP鎖:
- 如果 delete或者update, 則操作被阻塞, 但是可以看到具體加的只有X鎖:
可以看到, 其他事務只有在 insert 的時候, 才會加GAP鎖來防止幻讀, 所以delete/update 和 insert 是要區分開的.
不過, 后面學到mvcc的時候, 你會知道加鎖的低效性, 所以還有兩種解決方案:
使用隔離性的***隔離級別SERIALIZABLE, 但該隔離級別在實際中很少使用;
其實 REPEATABLE READ 就可以防止幻讀, 《高性能MySQL》中也說了, REPEATABLE READ 理論是是不能防止幻讀的, 但是由于該隔離級別還使用了MVCC, 可以做到非鎖定一致性讀取, 所以, 只要你真的確定你明白幻讀的意思, 你在 REPEATABLE READ隔離級別下是模擬不出幻讀效果的;
至于網絡上如下所謂的幻讀現象, 本人覺得是誤導, 剛開始本人也認為這就是REPEATABLE-READ隔離級別無法解決幻讀的鐵證, 后來發現錯了, 幻讀是指兩次讀操作發現記錄增多導致的不一致, 而如下是多次insert, 雖然也是個問題, 但已經不是 幻讀 問題了:
打開客戶端1查看隔離級別及初始數據
- 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查看隔離級別及初始數據
- 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中開啟事務, 然后查詢數據
- 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>
在客戶端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>
在客戶端2事務中再次查詢數據, 發現數據沒有變化(表示可以重復讀, 并且克服了幻讀), 但是在客戶端2事務中插入一條id為4的新數據, 發現提示數據已經存在, 注意, 雖然爆出問題來了, 但不是 幻讀 范疇
- 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>
那么這是什么問題呢?
個人認為, 如果你的表中真的會出現兩條完全相同的記錄, 考慮一下, 最起碼的表規范(第二范式)是否先滿足一下?