Mysql事务隔离级别及MVCC(多版本并发控制)
一、MySQL事務隔離級別
先注明一點:以下討論都是在多事務并發的情境下討論的
事務的特性(InnoDB引擎才有事務):
ACID
原子性:一個事務不可再分割,要么都執行要么都不執行
一致性:一個事務執行會使數據從一個一致狀態切換到另外一個一致狀態
隔離性:一個事務的執行不受其他事務的干擾
持久性:一個事務一旦提交,則會永久的改變數據庫的數據.
? 事務隔離級別指的是在處理同一個數據的多個事務中,一個事務修改數據后,其他事務何時能看到修改后的結果。數據庫事務的隔離級別有4個,由低到高依次為Read uncommitted 、Read committed 、Repeatable read 、Serializable ,這四個級別可以逐個解決臟讀 、不可重復讀 、幻讀這幾類問題。
各級別解決的問題如下:
1.Read Uncommitted(能讀到未提交的數據)
2.Read committed(解決了臟讀的問題(如下圖,事務二最終讀取到的是a=2的正確數據),但當前會話只能讀取到其他事務提交的數據,未提交的數據讀不到,出現不可重復讀的問題))
3.Repeatable Read(可重讀)
這是MySQL的默認事務隔離級別,它確保同一事務的多個實例在并發讀取數據時,會看到同樣的數據行;但可能出現幻讀現象:當用戶讀取某一范圍的數據行時,另一個事務又在該范圍內插入了新行,當用戶再讀取該范圍的數據行時,會發現有新的“幻影” 行。InnoDB和Falcon存儲引擎通過多版本并發控制(MVCC)機制解決了該問題。
其實對于幻讀, MySQL的InnoDB引擎默認的RR級別已經通過MVCC自動幫我們解決了, 所以該級別下, 你也模擬不出幻讀的場景; 退回到 RC 隔離級別的話, 又容易把幻讀和不可重復讀搞混淆, 具體可以參考《高性能MySQL》對 RR 隔離級別的描述, 理論上RR級別是無法解決幻讀的問題, 但是由于InnoDB引擎的RR級別還使用了MVCC, 所以也就避免了幻讀的出現!但是MVCC雖然解決了幻讀問題, 但嚴格來說只是解決了部分幻讀問題
解決不可重復讀
出現幻讀
4.serializable
在該隔離級別下,只允許一個事務在執行,其它事務必須等待這個事務執行完后才能執行,也就解決了上面的問題,但是效率太低了,沒有并發,只是單純的串行。
例:事務A操作時將整張表鎖住了,當事務B嘗試操作時,會被阻塞,直到事務A提交commit以后,事務B的操作才會返回結果,而且事務B等待的時間可以設置,超出時間就error。
不可重復讀和幻讀比較:
兩者有些相似,前者針對的是update或delete,后者針對的insert。
二、MVCC
1.MVCC:
全稱是Mutil-Version Concurrency Control,翻譯成中文是多版本并發控制,MySQL就利用了MVCC來判斷在一個事務中,哪個數據可以被讀出來,哪個數據不能被讀出來。
2.多版本:
在看MVCC之前,我們有必要知道另外一個知識點,數據庫存儲一行行數據,是分為兩個部分來存儲的,一個是數據行的額外信息(本篇博客不涉及),一個是真實的數據記錄,MySQL會為每一行真實數據記錄添加兩三個隱藏的字段:
非必須,如果表中有自定義的主鍵或者有Unique鍵,就不會添加row_id字段,如果兩者都沒有,MySQL會“自作主張”添加row_id字段。
必須,事務Id,代表這一行數據是由哪個事務id創建的。
必須,回滾指針,指向這行數據的上一個版本。
在這里需要著重說明下事務id,當我們開啟一個事務,并不會馬上獲得事務id,哪怕我們在事務中執行select語句,也是沒有事務id的(事務id為0),只有執行insert/update/delete語句才能獲得事務id,這一點尤為重要。其中和MVCC緊密相關的是transaction_id和roll_pointer兩個字段,在開發過程中,我們無需關心,但是要研究MVCC,我們必須關心。
如果有類似這樣的一行數據:
代表這行數據是由transaction_id為9的事務創建出來的,roll_pointer是空的,因為這是一條新紀錄。實際上,roll_pointer并不是空的,如果真要解釋,需要繞一大圈,理解成空的,問題也不大。
當我們開啟事務,對這條數據進行修改,會變成這樣:
有點感覺了吧,這就像一個單向鏈表,稱之為“版本鏈”,最上面的數據是這個數據的最新版本,roll_pointer指向這個數據的舊版本,給人的感覺就是一行數據有多個版本,是不是符合“多版本并發控制”中的“多版本”這個概念,
那么“并發控制”又是怎么做到的呢,別急,繼續往下看。
3.ReadView:
下面又要引出一個新的概念:ReadView。
對于READ UNCOMMITTED來說,可以讀取到其他事務還沒有提交的數據,所以直接把這個數據的最新版本讀出來就可以了,對SERIALIZABLE來說,是用加鎖的方式來訪問記錄。剩下的就是READ COMMITTED和REPEATABLE READ,這兩個事務隔離級別都要保證讀到的數據是其他事務已經提交的,也就是不能無腦把一行數據的最新版本給讀出來了,但是這兩個還是有一定的區別,最核心的問題就在于“我到底可以讀取這個數據的哪個版本”。為了解決這個問題,ReadView的概念就出現了,ReadView包含四個比較重要的內容:
有了這個ReadView,只要按照下面的判斷方式就可以解決“我到底可以讀取這個數據的哪個版本”這個千古難題了:
看完上面的描述,是不是覺得“云里霧里”,“不知所云”,甚至“腦闊疼,整個人都不好了”。
我們換個方法來解釋,看會不會更容易理解點:
在事務啟動的一瞬間(執行CURD操作),會創建出ReadView,對于一個數據版本的trx_id來說,有以下三種情況:
a.如果當前版本的trx_id在活躍事務列表中,代表這個版本是由還沒有提交的事務生成的,這個版本不可見;
b.如果當前版本的trx_id不在活躍事務列表中,代表這個版本是由已經提交的事務生成的,這個版本可見。
上面我比較簡單的解釋了下ReadView,用了兩種方式來說明如何判斷當前數據版本是否可見,不知道各位看官是不是有了一個比較模糊的概念,有了ReadView的基本概念,我們就可以具體看下READ COMMITTED、REPEATABLE READ這兩個事務隔離級別為什么讀到的數據是不同的,以及上述規則是如何應用的。
READ COMMITTED——每次讀取數據都會創建ReadView
假設,現在系統只有一個活躍的事務T,事務id是100,事務中修改了數據,但是還沒有提交,形成的版本鏈是這樣的:
現在A事務啟動,并且執行了select語句,此時會創建出一個ReadView,m_ids是【100】,min_trx_id是100, max_trx_id是101,creator_trx_id是0。為什么m_ids只有一個,為什么creator_trx_id是0?這里再次強調下,只有在事務中執行insert/update/delete語句才能獲得事務id。
那么A事務執行的select語句會讀到什么數據呢?
所以讀到的數據的name是“地底王”。
我們把事務T提交了,事務A再次執行select語句,此時,事務A再次創建出ReadView,m_ids是【】,min_trx_id是0, max_trx_id是101,creator_trx_id是0。
因為事務T已經提交了,所以沒有活躍的事務。
那么事務A第二次執行select語句又會讀到什么數據呢?
REPEATABLE READ ——首次讀取數據會創建ReadView
假設,現在系統只有一個活躍的事務T,事務id是100,事務中修改了數據,但是還沒有提交,形成的版本鏈是這樣的:
現在A事務啟動,并且執行了select語句,此時會創建出一個ReadView,m_ids是【100】,min_trx_id是100, max_trx_id是101,creator_trx_id是0。
那么A事務執行的select語句會讀到什么數據呢?
所以讀到的數據的name是“地底王”。
細心的你,一定發現了,這里我就是復制粘貼,因為在REPEATABLE READ事務隔離級別下,事務A首次執行select語句創建出來的ReadView和在READ COMMITTED事務隔離級別下,事務A首次執行select語句創建出來的ReadView是一樣的,所以判斷流程也是一樣的,所以我就偷懶了,copy走起。
隨后,事務T提交了事務,由于REPEATABLE READ是首次讀取數據才會創建ReadView,所以事務A再次執行select語句,不會再創建ReadView,用的還是上一次的ReadView,所以判斷流程和上面也是一樣的,所以讀到的name還是“地底王”。
參考:https://segmentfault.com/a/1190000014837747
參考:https://www.cnblogs.com/CodeBear/p/12710670.html
總結
以上是生活随笔為你收集整理的Mysql事务隔离级别及MVCC(多版本并发控制)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 互联网日报 | 7月3日 星期六 | 滴
- 下一篇: Mysql变量