我們都知道 MySQL 是支持多事務并發執行的,否則一個事務一個事務串行化處理,用戶都要砸鍵盤了,那么,多個事務同時寫一行資料怎么處理?一個事務在寫資料的時候,另一個事務要讀,又該怎么處理這個沖突?為了解決這些問題,MySQL 使用了 MVCC 多版本控制機制、事務隔離機制、鎖,
最耳熟能詳的就是,事務可以分成 4 個隔離級別:讀未提交、讀已提交、可重復讀、串行化,用的最多的就是 InnoDB 默認的隔離級別——是可重復讀 REPEATABLE READ,一般會叫它的縮寫「RR」,
?? 到這里,不知道你有沒有產生幾個疑問?
- 我們知道有事務隔離這回事兒,那為什么要隔離?為什么隔離還不夠,還要分級?
- 事務隔離級別解決了什么問題?沒有解決什么問題?
- MySQL 是如何實作這幾個隔離級別的呢?它們底層的作業原理是什么呢?
多個事務并發執行,是怎么一個場景呢?
不知道你第一次聽到「事務隔離機制」的時候是怎么想的,我的第一反應就是:好好的事務,為什么要給它隔離呢??
多個事務并發執行的場景是這樣的,我們有一個業務系統,里面很多執行緒在執行業務代碼,比如說 Service 去呼叫 Dao,Dao 去操作資料庫,在業務代碼層面,對資料庫的操作我們可能會給它加上事務,加上事務之后,如果執行成功就 commit,執行失敗就要 rollback,
上面這個流程大家肯定很熟了吧,接下來,由于 MySQL 中的資料是保存在磁盤上,但你要知道,隨機讀磁盤是很耗時間的,對于頻繁的 IO 操作,通常的做法就是先把資料加載到記憶體里面,MySQL 中就有個記憶體組件 Buffer Pool,執行增刪改查的時候,都會把資料從磁盤加載到 Buffer Pool 中,再執行增刪改查操作,
現在要來操作記憶體(Buffer Pool)中的資料了,由于 MySQL 要支持事務,它是通過什么實作事務的呢?這就涉及到 undo log、redo log來支持 rollback 和 commit 操作了,
上面講的是一個事務執行的大致流程,那假設這里的每個執行緒都開啟一個事務,那此時就是多個事務并發執行的場景了,

現在你知道多個事務并發執行是怎么一回事兒了,那又有第二個問題了:多個事務并發又咋了,有什么問題么?又沒多吃你家一塊肉!實際上這里是有問題,因為允許多個事務并發執行,那它們就可能同時訪問同一行資料,這就會發生并發問題了,
- 寫沖突,多個事務同時對快取頁里面的一行資料進行更新,允許誰來寫?這個沖突要怎么解決?可不可以用鎖來解決?
- 讀寫沖突,一個事務在寫資料,別的事務過來讀資料了,這個時候要怎么辦?
MySQL 解決它們的方法就是 MVCC 多版本控制機制、事務隔離級別、鎖機制,也是后面幾篇要介紹的內容,
8張圖告訴你,臟寫、臟讀、不可重復讀、幻讀到底是怎么回事兒
現在你已經知道了多個事務并發執行是怎么樣的一個場景,也知道這樣會產生各種沖突,有事務在寫資料的時候,別的事務要讀同一行的資料那怎么辦?一個事務寫到一半反悔了,要回滾又會產生什么問題?
這種讀寫沖突可能導致的問題,前輩都幫我們總結好了,就是臟寫、臟讀、不可重復讀和幻讀的問題,
臟寫
MySQL 的資料是放在一個個快取頁里面的,然后每個快取頁里面是一行行的資料,就像下面這張圖這樣:

現在有一個事務 A 正在執行,它執行的是一個寫操作,原來有一行資料是 NULL,在它執行了 update 操作,把 NULL 改成了值 A,就像下面這張圖這樣:

注意,這里事務 A 更新了一行資料但是它并沒有提交,緊接著事務 B 也來寫這行資料了,這就是多個事務并發執行,還操作同一行資料的場景了,事務 B 做的也是 update 操作,把值 A 改成了值 B,如下圖所示:

前面說了事務 A 此時是沒有提交的,除了提交事務,還可以干嘛?對了,事務 A 是可以回滾的!回滾意味著什么?回滾是不是就意味著原來那一行資料,要回滾到事務 A 執行之前的那個值,也就是 NULL:

事務 A 回滾了,對于事務 B 意味著什么?事務 B 明明正常寫了一行資料,但是寫完之后發現值變了,變成一個莫名其妙的值,
這就是臟寫,臟寫就是說我兩個事務來寫同一行資料,但是前面的那個事務反悔了,回滾了,在后面的事務 B 眼里,我明明修改了資料,怎么會寫錯呢?它打算也想不到,是別的事務回滾了,
臟讀
臟讀的情況和臟寫差不多嘍:

- 事務 A 先寫資料,把一行資料的值從 null 改成了 A,同樣事務 A 并沒有提交;
- 然后事務 B 過來讀了,它讀到的值自然是 A 嘍;
- 接著事務 A 又回滾了!回滾之后值就要從 A 變回到 NULL;
- 事務 B 再去讀的時候讀到的就是 NULL 了
臟讀就是事務 B 因為事務 A 回滾,讀不到之前的值了,
不可重復讀

- 事務 A 先去讀一行資料,讀到值是 A;
- 事務 B 去修改資料,改成了 B,這里和前面不一樣的地方就在,事務 B 它還提交了,不回滾了,
- 事務 A 第二次去讀,讀到的是 B,和第一次讀到的 A 不一樣,
那不可重復讀是指什么?它是指在同一個事務里面查詢同一行資料,每次查到的資料都不一樣,是不是和臟讀很像,區別在于臟讀是由于別的事務回滾導致,而不可重復讀讀到的其實是已經提交的資料,
幻讀

最后就剩下幻讀了,前面的臟寫、臟讀、不可重復讀,都是針對一行資料來說的,幻讀不一樣,幻讀是指查到了之前沒有的一批資料:
- 事務 A 里有一個條件查詢的陳述句
select name from t where id > 10,它進行了一次范圍查詢,查到了 10 行資料; - 然后事務 B 網里面加入了一批資料
- 事務 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 版本控制機制是到底是什么意思,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/4588.html
標籤:MySQL
上一篇:MySQL事務
