MySQL InnoDB引擎在Repeatable Read(可重複讀)隔離級別下,到底有沒有解決幻讀的問題?
網上眾說紛紜,有的說解決了,有的說沒解決,甚至有些大v的意見都無法達成統一。
今天就深入剖析一下,徹底解決這個幻讀的問題。
解決幻讀問題之前,先普及幾個知識點。
先建立一張使用者表,用作資料驗證:
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最新插入的資料,這種情況就屬於幻讀。
再普及一下快照讀和當前讀。
快照讀: 讀取資料的歷史版本,不對資料加鎖。
例如:select
當前讀: 讀取資料的最新版本,並對資料進行加鎖。
例如:insert、update、delete、select for update、select lock in share mode。
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),防止其他事務修改資料,這樣也就徹底解決了幻讀問題。
你覺得有什麼好辦法嗎?