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