MySQL到底有沒有解決幻讀問題?這篇文章徹底給你解答

2022-09-13 15:00:38

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),防止其他事務修改資料,這樣也就徹底解決了幻讀問題。

你覺得有什麼好辦法嗎?