MySQL的RR隔離級別與幻讀問題

最近在網(wǎng)上看了不少mysql鎖的文章,不少文章都提到InnoDB的RR隔離級別(Repeatable Read)無法解決幻讀的問題。對此問題作者親自做了一些實驗,將實驗結(jié)論記錄在此。

本次實驗的mysql版本為5.7.22

理解本次實驗所需具備的基礎(chǔ)知識

此篇文章的重點在于通過實驗的形式解釋清楚InnoDB的RR隔離級別是否解決了幻讀問題。所以文中將不會對一些相關(guān)的概念進行解釋,默認讀者已經(jīng)具備相關(guān)知識。如果讀者對于以下的知識點不甚清楚,最好自行查閱相關(guān)資料,理解清楚之后再閱讀接下來的實驗內(nèi)容,以免造成困惑。

進行此次實驗需要具備的知識點(包括但不限于):

  • InnoDB的事務(wù)
  • InnoDB的MVVC(Multi-Version Concurrency Control,多版本并發(fā)控制)
  • InnoDB存儲引擎的四種隔離級別
  • 臟讀、可重復讀和幻讀的概念
  • InnoDB的鎖機制(共享鎖S,排它鎖X,表鎖,行鎖)
  • next-key lock和gap lock

測試用數(shù)據(jù)表

創(chuàng)建表結(jié)構(gòu):

CREATE TABLE tmp_table (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255));

創(chuàng)建兩條數(shù)據(jù):

INSERT INTO tmp_table (name) values ('andy'),('jerry');

最終的表數(shù)據(jù)如下:

id name
1 andy
2 jerry

進行實驗

打開兩個終端,連上mysql,分別啟動事務(wù)a和事務(wù)b。

在事務(wù)a和事務(wù)b上面分別執(zhí)行如下命令:

事務(wù)a 事務(wù)b
start transaction; start transaction;
insert into tmp_table (name) values ('newa'); --
-- select * from tmp_table;
select * from tmp_table; --

查詢出來的結(jié)果如下:
事務(wù)a:

id name
1 andy
2 jerry
3 newa

事務(wù)b:

id name
1 andy
2 jerry

很明顯事務(wù)b沒有查詢到事務(wù)a未提交的新插入數(shù)據(jù)。原因也很簡單,因為普通的select語句是快照讀,而事務(wù)b啟動時,它的快照數(shù)據(jù)就已經(jīng)被版本鎖定了。

如果事務(wù)b進行當前讀是否能夠讀取到事務(wù)a未提交的新插入數(shù)據(jù)呢?

那么我們在事務(wù)b里面執(zhí)行如下命令來看看執(zhí)行結(jié)果:

select * from tmp_table lock in share mode;

執(zhí)行完成之后我們發(fā)現(xiàn)事務(wù)b此時會block住,原因是事務(wù)a的insert語句排它鎖住了id為3的新插入數(shù)據(jù),而事務(wù)b想請求所有行的共享鎖,肯定是需要等待的。

那么此時事務(wù)b當前讀id為1或2的數(shù)據(jù)(非事務(wù)a新插入數(shù)據(jù))是否可行呢?
結(jié)論是可行的,因為tmp_table存在唯一鍵,且事務(wù)a的insert語句只是鎖住了id為3的行。所以其他事務(wù)獲取其他行的共享鎖是可行的。讀者可以自行測試,這里就不做演示了。

事務(wù)a提交之后,事務(wù)b此時能否讀取到事務(wù)a新插入的數(shù)據(jù)呢?

事務(wù)a和事務(wù)b執(zhí)行如下命令:

事務(wù)a 事務(wù)b
commit; --
-- select * from tmp_table;

事務(wù)b打印的結(jié)果:

id name
1 andy
2 jerry

還是一樣,因為普通select是快照讀,事務(wù)b還是讀取到的是快照數(shù)據(jù),所以不包含事務(wù)a提交之后的新數(shù)據(jù)。

如果此時事務(wù)b使用當前讀,能否獲取到事務(wù)a已提交的新插入數(shù)據(jù)呢?

讓我們在事務(wù)b下面使用共享鎖查看當前版本數(shù)據(jù):

select * from tmp_table lock in share mode;

結(jié)果如下:

id name
1 andy
2 jerry
3 newa

可以查詢到事務(wù)a已提交的新數(shù)據(jù),所以此時使用當前讀就產(chǎn)生了幻讀。

另一種情況的幻讀

還有另一種情況也會產(chǎn)生幻讀,并且只需要執(zhí)行普通的select語句。下面請看演示。

在事務(wù)b下面執(zhí)行如下兩條語句:

update tmp_table set name='bbb' where id = 3;  
select * from tmp_table;

第一條命令使用update更新了事務(wù)a已提交的新數(shù)據(jù),第二條命令通過普通的select語句查看快照數(shù)據(jù)。

打印結(jié)果如下:

id name
1 andy
2 jerry
3 bbb

可以看到事務(wù)a已提交的新數(shù)據(jù)被事務(wù)b使用update語句更新了,并且通過普通的select語句給查詢出來了,很顯然,出現(xiàn)了幻讀

結(jié)論

所以說InnoDB的RR隔離級別沒有或者解決了幻讀問題都不太準確。應(yīng)該說它并沒有完全解決幻讀的問題。

如果在同一個事務(wù)里面,只是總是執(zhí)行普通的select快照讀,是不會產(chǎn)生幻讀的。

但是如果在這個事務(wù)里面通過當前讀或者先更新然后快照讀的形式來讀取數(shù)據(jù),就會產(chǎn)生幻讀。

參考

Phantom Rows
Innodb 中 RR 隔離級別能否防止幻讀?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容