【糾錯系列】不可重復讀與幻讀的區別

糾錯

我猜,你們在各種博文中看到對于幻讀的解釋是這樣的:

一個事務按相同的查詢條件重新讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱為“幻讀”。
即事務A 執行兩次 select 操作得到不同的數據集,即 select 1 得到 10 條記錄,select 2 得到 11 條記錄。

這其實并不是幻讀,這是不可重復讀的一種,只會在 R-U R-C 級別下出現,而在 mysql 默認的 RR 隔離級別是不會出現的(下面會舉例推翻)。

然而,我終于在茫茫文章中,找到了相對正確的解釋:

幻讀,并不是說兩次讀取獲取的結果集不同,幻讀側重的方面是某一次的 select 操作得到的結果所表征的數據狀態無法支撐后續的業務操作
更為具體一些:select 某記錄是否存在,不存在,準備插入此記錄,但執行 insert 時發現此記錄已存在,無法插入,此時就發生了幻讀。

推翻錯誤的解釋

事務隔離級別

mysql 有四級事務隔離級別 每個級別都有字符或數字編號

讀未提交 READ-UNCOMMITTED | 0:存在臟讀,不可重復讀,幻讀的問題

讀已提交 READ-COMMITTED | 1:解決臟讀的問題,存在不可重復讀,幻讀的問題

可重復讀 REPEATABLE-READ | 2:解決臟讀,不可重復讀的問題,存在幻讀的問題,默認隔離級別,使用 MMVC機制 實現可重復讀

序列化 SERIALIZABLE | 3:解決臟讀,不可重復讀,幻讀,可保證事務安全,但完全串行執行,性能最低

幻讀會在 RU / RC / RR 級別下出現,SERIALIZABLE 則杜絕了幻讀,但 RU / RC 下還會存在臟讀,不可重復讀,故我們就以 RR 級別來研究幻讀,排除其他干擾。

舉例推翻

建表a,id列自增主鍵,name列唯一索引。

CREATE TABLE a (
  id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(255) DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY UIDX_NAME (name)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

準備初始數據:

INSERT INTO a VALUES(NULL,'a'),(NULL,'b');

MySQL隔離級別設置為RR(默認),準備兩個事務AB。然后依次執行:

  1. 開始事務A,執行查詢語句:
START TRANSACTION;
SELECT * FROM a WHERE name BETWEEN 'a' AND 'z';

+----+------+
| id | name |
+----+------+
|  1 | a    |
|  2 | b    |
+----+------+
2 rows in set
  1. 開啟事務B,執行插入語句,并提交
START TRANSACTION;
INSERT INTO a VALUES(NULL,'c'),(NULL,'d');
COMMIT;
  1. 事務A再次執行查詢語句:
SELECT * FROM a WHERE name BETWEEN 'a' AND 'z';

+----+------+
| id | name |
+----+------+
|  1 | a    |
|  2 | b    |
+----+------+
2 rows in set

小結

可以看到,在RR級別下,所謂的"幻讀"并沒有出現。而SQL-92標準中定義的RR級別是沒法解決幻讀的,這就是矛盾點所在。如果是所謂的"幻讀",事務A應該讀到abcd四條數據。出現這種情況有兩種可能:

  1. MySQL在RR級別解決了幻讀。
  2. 這不是真正的幻讀。

查閱MySQL官方文檔,并沒有某一段文字說明其在RR級別解決了幻讀問題。

https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html

所以說,其實這并不是“幻讀”的范疇,這仍然屬于RR級別所解決的,不可重復讀范疇。

解釋

我們能確定的是,RR級別解決了不可重復讀的問題。
那么為什么說上述例子屬于不可重復讀范疇呢?我們得從解決不可重復讀問題的原理MVCC講起。

MVCC

MySQL InnoDB存儲引擎,實現的是基于多版本的并發控制協議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對的,是基于鎖的并發控制,Lock-Based Concurrency Control)。MVCC最大的好處,相信也是耳熟能詳:讀不加鎖,讀寫不沖突。在讀多寫少的OLTP應用中,讀寫不沖突是非常重要的,極大的增加了系統的并發性能,這也是為什么現階段,幾乎所有的RDBMS,都支持了MVCC。

在MVCC并發控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當前讀 (current read)。快照讀,讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。當前讀,讀取的是記錄的最新版本,并且,當前讀返回的記錄,都會加上鎖,保證其他事務不會再并發修改這條記錄。

快照讀VS當前讀

在一個支持MVCC并發控制的系統中,哪些讀操作是快照讀?哪些操作又是當前讀呢?以MySQL InnoDB為例:

快照讀:簡單的select操作,屬于快照讀,不加鎖。

select * from table where ?;

當前讀:特殊的讀操作,插入/更新/刪除操作,屬于當前讀,需要加鎖。

select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;

所有以上的語句,都屬于當前讀,讀取記錄的最新版本。并且,讀取之后,還需要保證其他并發事務不能修改當前記錄,對讀取記錄加鎖。其中,除了第一條語句,對讀取記錄加S鎖 (共享鎖)外,其他的操作,都加的是X鎖 (排它鎖)。

為什么將 插入/更新/刪除 操作,都歸為當前讀?

一個Update操作的具體流程。當Update SQL被發給MySQL后,MySQL Server會根據where條件,讀取第一條滿足條件的記錄,然后InnoDB引擎會將第一條記錄返回,并加鎖 (current read)。待MySQL Server收到這條加鎖的記錄之后,會再發起一個Update請求,更新這條記錄。一條記錄操作完成,再讀取下一條記錄,直至沒有滿足條件的記錄為止。因此,Update操作內部,就包含了一個當前讀。同理,Delete操作也一樣。Insert操作會稍微有些不同,簡單來說,就是Insert操作可能會觸發Unique Key的沖突檢查,也會進行一個當前讀。

注:針對一條當前讀的SQL語句,InnoDB與MySQL Server的交互,是一條一條進行的,因此,加鎖也是一條一條進行的。先對一條滿足條件的記錄加鎖,返回給MySQL Server,做一些DML操作;然后在讀取下一條加鎖,直至讀取完畢。

總結

所以說,在上述例子中,事務A第二次讀取(快照讀)的是記錄的歷史版本。而不是最新的版本——事務B插入新紀錄后的abcd四條。這屬于不可重復讀的范疇。

有些博文錯誤的把這種情況歸為“幻讀”。甚至還有的說MySQL的RR級別解決了幻讀。在此糾錯。

參考:

https://segmentfault.com/a/1190000016566788?utm_source=tag-newest
http://hedengcheng.com/?p=771

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容

  • MySQL 加鎖處理分析 轉載2013年12月13日 16:43:55 7598 原文地址:http://hede...
    初來的雨天閱讀 461評論 0 2
  • 聲明:本文為學習總結篇,學習自登成大大的博客,這是一篇需要反復閱讀的文章,歡迎大家一起交流學習~原文出處:何登成的...
    Vechace閱讀 390評論 1 6
  • 背景 MySQL/InnoDB的加鎖分析,一直是一個比較困難的話題。我在工作過程中,經常會有同事咨詢這方面的問題。...
    云狗狗狗狗狗閱讀 229評論 1 1
  • 背景 1[1.1 MVCC:Snapshot Read vs Current Read 2][1.2 ...
    jerrik閱讀 451評論 0 2
  • 背景 MySQL/InnoDB的加鎖分析,一直是一個比較困難的話題。我在工作過程中,經常會有同事咨詢這方面的問題。...
    MakeACoder閱讀 618評論 0 3