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