周一的時候被問到了幻讀的問題,之前都是看別人寫的文章沒有建套環境來實際操作一下。
其實很多問題不僅是要看源碼,還是需要動動手,光看還是會忘記。
先說結論在忽略參數設置的情況下, MySQL 的確使用 MVCC 配合 Gap Lock 解決了 RR 隔離級別下的當前讀(用 Gap Lock)和快照讀(用 MVCC)的幻讀問題。
我們先來建立測試用的基礎表
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
MVCC 快照讀解決幻讀
然后 先來看看在 RR 級別下因為 MVCC 的關系解決幻讀的情況
session1 | session2 | session3 | |
line 1 | begin; select * from t where d =5; |
||
line 2 | update t set d = 5 where id = 0 | ||
line 3 | select * from t where d =5; | ||
line 4 | insert into t value(1, 1, 5) | ||
line 5 | select * from t where d =5; | ||
line 6 | commit; |
line 1 session 1
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where d = 5; +----+------+------+ | id | c | d | +----+------+------+ | 5 | 5 | 5 | +----+------+------+ 1 row in set (0.00 sec)
line 2 session 2 執行后 line 3 session 1
mysql> update t set d = 5 where id = 0; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from t where d = 5; +----+------+------+ | id | c | d | +----+------+------+ | 5 | 5 | 5 | +----+------+------+ 1 row in set (0.00 sec)
line 4 session3 執行后的 line 5 session 1
mysql> insert into t value (1, 1, 5); Query OK, 1 row affected (0.01 sec) mysql> select * from t where d = 5; +----+------+------+ | id | c | d | +----+------+------+ | 5 | 5 | 5 | +----+------+------+ 1 row in set (0.00 sec)
可以看到在快照讀的情況下, RR 隔離級別可以通過 MVCC 保護自己事務內的數據,無論外面如何修改。
Gap Lock 解決當前讀下的幻讀
當前讀的情況下我們就無法達到剛才的效果了,還是剛才那個流程
session1 | session2 | session3 | |
line 1 | begin; select * from t where d =5 for update; |
||
line 2 | update t set d = 5 where id = 0 | ||
line 3 | select * from t where d =5 for update; | ||
line 4 | insert into t value(1, 1, 5) | ||
line 5 | select * from t where d =5 for update; | ||
line 6 | commit; |
我為 session 1里的所有查詢都加上當前讀的 for update 。會發現從 session2 開始就被阻塞了。
這里就是在執行 select * from t where d = 5 for update 的時候因為 d 上沒有索引的關系,MySQL 直接將該字段的全部范圍都打上了 Gap Lock,所以無法執行插入更新操作,就阻塞住了。
可以看到通過 Gap Lock 我們即使在當前讀也被阻止了幻讀,因為 Gap Lock 的關系 session c 嘗試往里插入新數據也同樣被阻塞從而保證了數據的一致性。
所以回到文中開始的那個問題,MySQL 在 RR 事務隔離級別下,的確通過 MVCC 以及 Gap Lock 解決了幻讀問題,是我答錯了。