8張圖說清楚髒寫、髒讀、不可重複讀、幻讀究竟是怎麼回事兒

咱們都知道 MySQL 是支持多事務併發執行的,不然一個事務一個事務串行化處理,用戶都要砸鍵盤了。那麼,多個事務同時寫一行數據怎麼處理?一個事務在寫數據的時候,另外一個事務要讀,又該怎麼處理這個衝突?爲了解決這些問題,MySQL 使用了 MVCC 多版本控制機制、事務隔離機制、鎖。html

最耳熟能詳的就是,事務能夠分紅 4 個隔離級別:讀未提交、讀已提交、可重複讀、串行化。用的最多的就是 InnoDB 默認的隔離級別——是可重複讀 REPEATABLE READ,通常會叫它的縮寫「RR」。mysql

🤔 到這裏,不知道你有沒有產生幾個疑問?sql

  • 咱們知道有事務隔離這回事兒,那爲何要隔離?爲何隔離還不夠,還要分級?
  • 事務隔離級別解決了什麼問題?沒有解決什麼問題?
  • MySQL 是如何實現這幾個隔離級別的呢?它們底層的工做原理是什麼呢?

多個事務併發執行,是怎麼一個場景呢?

不知道你第一次聽到「事務隔離機制」的時候是怎麼想的,個人第一反應就是:好好的事務,爲何要給它隔離呢??數據庫

多個事務併發執行的場景是這樣的。咱們有一個業務系統,裏面不少線程在執行業務代碼,好比說 Service 去調用 Dao,Dao 去操做數據庫。在業務代碼層面,對數據庫的操做咱們可能會給它加上事務,加上事務以後,若是執行成功就 commit,執行失敗就要 rollback緩存

上面這個流程你們確定很熟了吧。接下來,因爲 MySQL 中的數據是保存在磁盤上。但你要知道,隨機讀磁盤是很耗時間的,對於頻繁的 IO 操做,一般的作法就是先把數據加載到內存裏面。MySQL 中就有個內存組件 Buffer Pool,執行增刪改查的時候,都會把數據從磁盤加載到 Buffer Pool 中,再執行增刪改查操做。多線程

如今要來操做內存(Buffer Pool)中的數據了,因爲 MySQL 要支持事務,它是經過什麼實現事務的呢?這就涉及到 undo log、redo log來支持 rollback 和 commit 操做了。併發

上面講的是一個事務執行的大體流程,那假設這裏的每一個線程都開啓一個事務,那此時就是多個事務併發執行的場景了。spa

併發訪問數據的場景

如今你知道多個事務併發執行是怎麼一回事兒了,那又有第二個問題了:多個事務併發又咋了,有什麼問題麼?又沒多吃你家一塊肉!實際上這裏是有問題,由於容許多個事務併發執行,那它們就可能同時訪問同一行數據,這就會發生併發問題了。線程

  • 寫衝突,多個事務同時對緩存頁裏面的一行數據進行更新,容許誰來寫?這個衝突要怎麼解決?可不能夠用鎖來解決?
  • 讀寫衝突,一個事務在寫數據,別的事務過來讀數據了,這個時候要怎麼辦?

MySQL 解決它們的方法就是 MVCC 多版本控制機制、事務隔離級別、鎖機制,也是後面幾篇要介紹的內容。版本控制

8張圖告訴你,髒寫、髒讀、不可重複讀、幻讀究竟是怎麼回事兒

如今你已經知道了多個事務併發執行是怎麼樣的一個場景,也知道這樣會產生各類衝突。有事務在寫數據的時候,別的事務要讀同一行的數據那怎麼辦?一個事務寫到一半反悔了,要回滾又會產生什麼問題?

這種讀寫衝突可能致使的問題,前輩都幫咱們總結好了,就是髒寫、髒讀、不可重複讀和幻讀的問題。

髒寫

MySQL 的數據是放在一個個緩存頁裏面的,而後每一個緩存頁裏面是一行行的數據,就像下面這張圖這樣:

緩存頁

如今有一個事務 A 正在執行,它執行的是一個寫操做,原來有一行數據是 NULL,在它執行了 update 操做,把 NULL 改爲了值 A,就像下面這張圖這樣:

事務A更新一行數據

注意,這裏事務 A 更新了一行數據可是它並無提交。緊接着事務 B 也來寫這行數據了,這就是多個事務併發執行,還操做同一行數據的場景了。事務 B 作的也是 update 操做,把值 A 改爲了值 B,以下圖所示:

事務B併發執行

前面說了事務 A 此時是沒有提交的,除了提交事務,還能夠幹嗎?對了,事務 A 是能夠回滾的!回滾意味着什麼?回滾是否是就意味着原來那一行數據,要回滾到事務 A 執行以前的那個值,也就是 NULL:

事務A回滾了!

事務 A 回滾了,對於事務 B 意味着什麼?事務 B 明明正常寫了一行數據,可是寫完以後發現值變了,變成一個莫名其妙的值。

這就是髒寫,髒寫就是說我兩個事務來寫同一行數據,可是前面的那個事務反悔了,回滾了。在後面的事務 B 眼裏,我明明修改了數據,怎麼會寫錯呢?它打算也想不到,是別的事務回滾了。

髒讀

髒讀的狀況和髒寫差很少嘍:

髒讀

  1. 事務 A 先寫數據,把一行數據的值從 null 改爲了 A,一樣事務 A 並無提交;
  2. 而後事務 B 過來讀了,它讀到的值天然是 A 嘍;
  3. 接着事務 A 又回滾了!回滾以後值就要從 A 變回到 NULL;
  4. 事務 B 再去讀的時候讀到的就是 NULL 了

髒讀就是事務 B 由於事務 A 回滾,讀不到以前的值了。

不可重複讀

不可重複讀

  1. 事務 A 先去讀一行數據,讀到值是 A;
  2. 事務 B 去修改數據,改爲了 B。這裏和前面不同的地方就在,事務 B 它還提交了,不回滾了。
  3. 事務 A 第二次去讀,讀到的是 B,和第一次讀到的 A 不同。

那不可重複讀是指什麼?它是指在同一個事務裏面查詢同一行數據,每次查到的數據都不同。是否是和髒讀很像,區別在於髒讀是因爲別的事務回滾致使,而不可重複讀讀到的實際上是已經提交的數據。

幻讀

幻讀

最後就剩下幻讀了,前面的髒寫、髒讀、不可重複讀,都是針對一行數據來講的,幻讀不同,幻讀是指查到了以前沒有的一批數據:

  1. 事務 A 裏有一個條件查詢的語句 select name from t where id > 10,它進行了一次範圍查詢,查到了 10 行數據;
  2. 而後事務 B 網裏面加入了一批數據
  3. 事務 A 再查的用條件查詢語句查詢的時候,發現查到了 15 條,其中 5 條是以前沒見過的。這個事務 A 覺得本身出現幻覺了,怎麼會多出這麼些個數據?這就是幻讀了。

事務隔離機制是如何解決髒寫、髒讀、不可重複讀和幻讀問題的

前面講到了,多事務併發執行是會帶來髒寫、髒讀、不可重複讀和幻讀的問題,MySQL 是如何解決這個問題的呢?答案其實每一個人都聽過,就是使用事務隔離機制,包括 read uncommitted(讀未提交),read committed(讀已提交),repeatable read(可重複讀),serializable(串行化)這幾個隔離級別。

咱們回過頭來看髒寫和髒讀。它們其實都是後面的事務正常執行,可是前面的事務回滾了致使的。這個時候它們就是處在 read uncommitted(讀未提交)這個隔離級別之下,可以讀到別人沒有提交的事務。

髒讀

那麼如今提升事務的隔離級別,變成 read committed(讀已提交)會怎麼樣呢?好比說髒讀,事務 A 修改了值,從 NULL 變成了值 A。這個時候事務 B 來讀,因爲隔離級別是 RC,事務 A 沒有提交事務的狀況下,事務 B 是讀不到的,也就不存在事務 A 回滾致使事務 B 第二次讀到值和第一次不同了。

不可重複讀和幻讀則與髒寫和髒讀有所區別了,它們讀到的都是已經提交了的事務。可是在 repeatable read(可重複讀)這個隔離級別中,就可以作到事務開啓以後,不論別的事務是否提交,它讀到的值都和最開始同樣。這就要基於 MVCC 多版本控制機制來說了,在這裏先賣個關子,後面會專門寫一篇文章來說 MySQL 的 RR 是如何實現的。

最後就是 serializable(串行化),串行化很好理解,就是一次只能執行一個事務,徹底禁止併發,你們排好隊一個個執行,啥事兒都沒有嘍。但實際開發中是不會有人用這個隔離級別的,重點再念一遍「RR」repeatable read(可重複讀)。

小結

先回顧一下,這篇文章先講了多事務併發執行的場景是怎麼樣的?緊接着引出了多事務併發可能帶來的問題,其中就包括髒寫、髒讀、不可重複讀和幻讀。最後介紹了一個事務的隔離級別:read uncommitted(讀未提交),read committed(讀已提交),repeatable read(可重複讀),serializable(串行化),以及它們是如何解決髒寫、髒讀、不可重複讀和幻讀問題的。

文章中還留下一個小坑沒有填,那就是 MySQL 中最經常使用到的隔離級別可重複讀是怎麼實現,MVCC 版本控制機制是究竟是什麼意思。

相關文章
相關標籤/搜索