MySQL InnoDB引擎在Repeatable Read(可重覆讀)隔離級別下,到底有沒有解決幻讀的問題? 網上眾說紛紜,有的說解決了,有的說沒解決,甚至有些大v的意見都無法達成統一。 今天就深入剖析一下,徹底解決這個幻讀的問題。 解決幻讀問題之前,先普及幾個知識點。 ...
MySQL InnoDB引擎在Repeatable Read(可重覆讀)隔離級別下,到底有沒有解決幻讀的問題?
網上眾說紛紜,有的說解決了,有的說沒解決,甚至有些大v的意見都無法達成統一。
今天就深入剖析一下,徹底解決這個幻讀的問題。
解決幻讀問題之前,先普及幾個知識點。
1. 併發事務產生的問題
先創建一張用戶表,用作數據驗證:
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='用戶表';
併發事務會產生下麵三個問題:
臟讀
定義: 一個事務讀到其他事務未提交的數據。
從上面的示例圖中,可以看出,在事務2修改完數據,沒有提交的情況。事務1已經讀到事務2最新修改的數據,這種情況就屬於臟讀。
不可重覆讀
定義: 一個事務讀取到其他事務修改過的數據。
從上面的示例圖中,可以看出,在事務2修改完數據,並提交事務後。事務1第二次查詢已經讀到事務2最新修改的數據,這種情況就屬於不可重覆讀。
幻讀
定義: 一個事務讀取到其他事務最新插入的數據。
從上面的示例圖中,可以看出,在事務2插入完數據,並提交事務後。事務1第二次查詢已經讀到事務2最新插入的數據,這種情況就屬於幻讀。
2. 快照讀和當前讀
再普及一下快照讀和當前讀。
快照讀: 讀取數據的歷史版本,不對數據加鎖。
例如:select
當前讀: 讀取數據的最新版本,並對數據進行加鎖。
例如:insert、update、delete、select for update、select lock in share mode。
3. 再談幻讀問題
MySQL在Repeatable Read(可重覆讀)隔離級別下,到底有沒有解決幻讀的問題?
只能說是部分解決了幻讀問題。
首先,在快照讀的情況下,是通過MVCC(復用讀視圖)解決了幻讀問題。
想詳細瞭解MVCC和讀視圖,可以翻一下上篇文章。
先手動設置一下MySQL的隔離級別為可重覆讀:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
執行測試用例,驗證一下:
從上面的示例圖中,可以看出,事務1的兩次查詢,得到的結果一致,並沒有查到事務2最新插入的數據。
原因是,在可重覆讀隔離級別下,第一次快照讀的時候,生成了一個讀視圖。第二次快照讀的時候,復用了第一次生成的讀視圖,所以兩次查詢得到的結果一致。
所以,在快照讀的情況下,可重覆讀隔離級別是解決了幻讀的問題。
再測試一下,在當前讀的情況下,可重覆讀隔離級別是否解決幻讀問題:
從上面的示例圖中,可以看出,事務1的兩次查詢,得到的結果不一致。在事務2插入數據,並提交事務後。事務1的第二次執行當前讀(加了for update)的時候,讀到了事務2最新插入的數據。
原因是,在可重覆讀隔離級別下,每次執行當前讀會生成一個新的讀視圖,所以能讀到其他事務最新插入的數據。
所以,在當前讀的情況下,可重覆讀隔離級別是沒有解決了幻讀的問題。
在執行上面的測試用例的時候,我忽然想到一個問題,既然select for update的當前讀,出現了幻讀問題,是不是其他的當前讀也會復現幻讀問題,比如insert。
再執行測試用例,驗證一下:
跟預想的一樣,在insert當前讀的情況下,也出現了幻讀的問題(主鍵衝突)。
那有沒有什麼辦法?在可重覆讀隔離級別下,執行當前讀的時候,也能解決幻讀的問題?
當然有的,唯一的辦法就是加鎖。
事務1在執行第一次查詢的時候,就對數據進行加鎖(使用for update),防止其他事務修改數據,這樣也就徹底解決了幻讀問題。
你覺得有什麼好辦法嗎?