一文搞懂MySQL事務(wù)的隔離性如何實(shí)現(xiàn)|MVCC

## 前言

MySQL有ACID四大特性必怜,本文著重講解**MySQL不同事務(wù)之間的隔離性**的概念,以及MySQL如何實(shí)現(xiàn)隔離性后频。下面先羅列一下MySQL的四種事務(wù)隔離級別棚赔,以及不同隔離級別可能會(huì)存在的問題。**事務(wù)隔離級別越高徘郭,多個(gè)事務(wù)在并發(fā)訪問數(shù)據(jù)庫時(shí)互相產(chǎn)生數(shù)據(jù)干擾的可能性越低,但是并發(fā)訪問的性能就越差**丧肴。(相當(dāng)于犧牲了一定的性能去保證數(shù)據(jù)的安全性)

下面這張表残揉,展示了MySQL的四大隔離級別和伴隨著的一些問題,下面詳細(xì)介紹芋浮。

![image.png](https://upload-images.jianshu.io/upload_images/27804186-75ddfe7d5319c2d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

## 事務(wù)隔離級別

讀未提交:多個(gè)事務(wù)同時(shí)修改一條記錄抱环,A事務(wù)對其的改動(dòng)在A事務(wù)還沒提交時(shí)壳快,在B事務(wù)中就可以看到A事務(wù)對其的改動(dòng)。

讀已提交:多個(gè)事務(wù)同時(shí)修改一條記錄镇草,A事務(wù)對其的改動(dòng)在A事務(wù)提交之后眶痰,在B事務(wù)中可以看到A事務(wù)對其的改動(dòng)。

可重復(fù)讀:多個(gè)事務(wù)同時(shí)修改一條記錄梯啤,這條記錄在A事務(wù)執(zhí)行期間是不變的(別的事務(wù)對這條記錄的修改不被A事務(wù)感知)竖伯。

串行化:多個(gè)事務(wù)同時(shí)訪問一條記錄(CRUD),讀加讀鎖因宇,寫加寫鎖,完全退化成了串行的訪問察滑,自然不會(huì)收到任何其他事務(wù)的干擾,性能最低户盯。

## 不同級別伴隨的問題

臟讀:A事務(wù)在提交前對一個(gè)字段的改動(dòng)會(huì)被B事務(wù)感知,那么事務(wù)之間就很容易產(chǎn)生干擾饲化,假如A對一個(gè)字段改動(dòng)之后被B感知莽鸭,但是A又回滾了事務(wù)滓侍,則對該字段的改動(dòng)依舊保留在B的查詢結(jié)果中,那么這樣的數(shù)據(jù)就是臟數(shù)據(jù)(處于處理中間過程的數(shù)據(jù))撩笆。

不可重復(fù)讀:A事務(wù)對于一條記錄的讀取結(jié)果捺球,在B事務(wù)對其修改并提交之后,A再次讀取同一條記錄會(huì)得到不同的結(jié)果夕冲。

幻讀:側(cè)重于A事務(wù)的同一個(gè)范圍查詢命令,前后兩次得到不同的記錄數(shù)量泣栈,原因是B事務(wù)可能對其進(jìn)行了插入弥姻。

### 小結(jié)一下

通過閱讀上面給出的內(nèi)容,可以得到結(jié)論:

1.? 讀未提交隔離級別并沒有對行數(shù)據(jù)的可見性做任何限制疼进,所有事務(wù)之間的改動(dòng)都是互相可見的,所以存在很多問題伞广,不推薦使用;

2.? 串行化隔離級別因?yàn)橥ㄟ^鎖機(jī)制對記錄的訪問進(jìn)行限制减拭,所以安全性最高区丑,但并發(fā)訪問退化成串行訪問,性能較低既们;

**因此本文將側(cè)重于探究MySQL如何實(shí)現(xiàn)`讀已提交`和`可重復(fù)讀`兩種隔離級別(也就是你聽聞的MVCC多版本并發(fā)控制的實(shí)現(xiàn))正什,通過后面的學(xué)習(xí)你將理解`讀已提交`隔離級別如何`解決臟讀`,`可重復(fù)讀`隔離級別如何更進(jìn)一步`解決不可重復(fù)讀`斯棒。**

**接下來我將向你介紹`undo 版本鏈`機(jī)制以及`read view`快照讀機(jī)制主经,這兩個(gè)機(jī)制相互配合是實(shí)現(xiàn)MVCC的核心,而`讀已提交`和`可重復(fù)讀`隔離級別的實(shí)現(xiàn)都是建立在這兩個(gè)核心機(jī)制之上穗酥。**

## undo 版本鏈

undo 版本鏈就是指undo log的存儲(chǔ)在邏輯上的表現(xiàn)形式惠遏,它被用于事務(wù)當(dāng)中的**回滾操作**以及**實(shí)現(xiàn)MVCC**,這里介紹一下undo log之所以能實(shí)現(xiàn)回滾記錄的原理抽高。

對于每一行記錄透绩,會(huì)有兩個(gè)隱藏字段:`row_trx_id`和`roll_pointer`,`row_trx_id`表示更新(改動(dòng))本條記錄的全局事務(wù)id?**(每個(gè)事務(wù)創(chuàng)建都會(huì)分配id碳竟,全局遞增狸臣,因此事務(wù)id區(qū)別對某條記錄的修改是由哪個(gè)事務(wù)作出的)**?,`roll_pointer`是回滾指針统翩,指向當(dāng)前記錄的前一個(gè)`undo log版本`此洲,如果是第一個(gè)版本則`roll_pointer`指向nil,這樣如果有多個(gè)事務(wù)對同一條記錄進(jìn)行了多次改動(dòng)娶桦,則會(huì)在`undo log`中以鏈的形式存儲(chǔ)改動(dòng)過程汁汗。

假如有兩個(gè)事務(wù)AB,數(shù)據(jù)表中有一行id為1的記錄祈争,其字段a初始值為0角寸,事務(wù)A對id=1的行的a修改為1,事務(wù)B對id=1的行的a字段修改為2沮峡,則`undo log版本鏈`記錄如下:

![image.png](https://upload-images.jianshu.io/upload_images/27804186-fba593d990592d6d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

在上圖中亿柑,最下方的undo log中記錄了當(dāng)前行的最新版本,而該條記錄之前的版本則以版本鏈的形式可追溯疟游,這也是事務(wù)回滾所做的事式矫。那undo log版本鏈和事務(wù)的隔離性有什么關(guān)系呢?**那就要引入另一個(gè)核心機(jī)制:read view聪廉。**

## read view

read view表示快照讀故慈,這個(gè)快照讀會(huì)記錄四個(gè)關(guān)鍵的屬性:

1.? `create_trx_id`: 當(dāng)前事務(wù)的id

2.? `m_idx`: 當(dāng)前正在活躍的所有事務(wù)id(id數(shù)組),沒有提交的事務(wù)的id

3.? `min_trx_id`: 當(dāng)前系統(tǒng)中活躍的事務(wù)的id最小值

4.? `max_trx_id`: 當(dāng)前系統(tǒng)中已經(jīng)創(chuàng)建過的最新事務(wù)(id最大)的id+1的值

**當(dāng)一個(gè)事務(wù)讀取某條記錄時(shí)會(huì)追溯undo log版本鏈干签,找到第一個(gè)可以訪問的版本拆撼,而該記錄的某一個(gè)版本是否能被這個(gè)事務(wù)讀取到遵循如下規(guī)則:(這個(gè)規(guī)則永遠(yuǎn)成立喘沿,這個(gè)需要好好理解蚜印,對后面講解可重復(fù)讀和讀已提交兩個(gè)級別的實(shí)現(xiàn)密切相關(guān))**

1.? 如果當(dāng)前記錄行的row_trx_id小于min_trx_id留量,表示該版本的記錄在當(dāng)前事務(wù)開啟之前創(chuàng)建,因此可以訪問到

2.? 如果當(dāng)前記錄行的row_trx_id大于等于max_trx_id忆绰,表示該版本的記錄創(chuàng)建晚于當(dāng)前活躍的事務(wù)可岂,因此不能訪問到

3.? 如果當(dāng)前記錄行的row_trx_id大于等于min_trx_id且小于max_trx_id,則要分兩種情況:

? ? *? 當(dāng)前記錄行的row_trx_id在m_idx數(shù)組中伐债,則當(dāng)前事務(wù)無法訪問到這個(gè)版本的記錄?**(除非這個(gè)版本的row_trx_id等于當(dāng)前事務(wù)本身的trx_id致开,本事務(wù)當(dāng)然能訪問自己修改的記錄)**?,在m_idx數(shù)組中又不是當(dāng)前事務(wù)自己創(chuàng)建的undo版本虹蒋,表示是并發(fā)訪問的其他事務(wù)對這條記錄的修改的結(jié)果飒货,則不能訪問到。

? ? *? 當(dāng)前記錄行的row_trx_id不在m_idx數(shù)組中晃虫,則表示這個(gè)版本是當(dāng)前事務(wù)開啟之前扣墩,其他事務(wù)已經(jīng)提交了的undo版本,當(dāng)前事務(wù)可訪問到荆责。

配合使用`read view`和`undo log版本鏈`就能實(shí)現(xiàn)**事務(wù)之間`并發(fā)訪問`相同記錄**時(shí)亚脆,可以根據(jù)事務(wù)id不同,獲取同一行的不同undo log版本(多版本并發(fā)控制)键耕。**下面通過模擬并發(fā)訪問的兩個(gè)事務(wù)操作**,介紹MVCC的實(shí)現(xiàn)(具體來說就是**可重復(fù)讀**和**讀已提交**兩個(gè)隔離級別的實(shí)現(xiàn))

### 可重復(fù)讀

下面模擬兩個(gè)并發(fā)訪問同一條記錄的事務(wù)AB的行為玛迄,假設(shè)這條記錄初始時(shí)id=1棚亩,a=0虏杰,該記錄兩個(gè)隱藏字段row_trx_id = 100,roll_pointer = nil

**注意:在可重復(fù)讀隔離級別下瘸彤,當(dāng)事務(wù)sql執(zhí)行的時(shí)候笛钝,會(huì)生成一個(gè)read view快照玻靡,且在本事務(wù)周期內(nèi)一直使用這個(gè)read view**,下面給出了并發(fā)訪問同一條記錄的兩個(gè)事務(wù)AB的具體執(zhí)行過程囤捻,并解釋`可重復(fù)讀`是如何實(shí)現(xiàn)的(解決了`臟讀`和`不可重復(fù)讀`)蝎土。

![image.png](https://upload-images.jianshu.io/upload_images/27804186-c96a43c954e495b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

事務(wù)A的read view:

`create_trx_id`?= 101|?`m_idx`?= [101, 102]|`min_trx_id`?= 101|`max_trx_id`?= 103

事務(wù)B的read view:

`create_trx_id`?= 102|?`m_idx`?= [101, 102]|`min_trx_id`?= 101|`max_trx_id`?= 103

(ps. 這里因?yàn)锳B事務(wù)是并發(fā)執(zhí)行,因此兩個(gè)事務(wù)創(chuàng)建的read view的max_trx_id = 103)

![image.png](https://upload-images.jianshu.io/upload_images/27804186-59e2d85a036520d0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

**這里要注意的是挡毅,每次對一條記錄發(fā)生修改暴构,就會(huì)記錄一個(gè)undo log的版本**,則在A事務(wù)中第二次查詢id=1的記錄的a的值的時(shí)候庆械,B事務(wù)對該記錄的修改已經(jīng)添加到版本鏈上了菌赖,此時(shí)這個(gè)`undo log`的`trx_id = 102`,在A事務(wù)的`read view`的`m_idx數(shù)組`中且不等于A事務(wù)的`trx_id = 101`堕绩,因此無法訪問到,需要在向前回溯特姐,這里找到`trx_id = 100`的記錄版本(小于A事務(wù)`read view`的`min_trx_id`屬性黍氮,因此可以訪問到),故A事務(wù)第二次查詢依舊得到a = 0捷枯,而不是B事務(wù)修改的a = 1专执。

你可能有疑問,在A事務(wù)第二次查詢的時(shí)候攀痊,B事務(wù)已經(jīng)完成提交了拄显,那么A事務(wù)的read view的m_idx數(shù)組應(yīng)該移除102才對啊,它存的不是當(dāng)前活躍的事務(wù)的id嗎涩笤?·

**注意:在可重復(fù)讀隔離級別下盒件,當(dāng)事務(wù)sql執(zhí)行的時(shí)候,會(huì)生成一個(gè)read view快照恩沽,且在本事務(wù)周期內(nèi)一直使用這個(gè)read view**翔始,雖然102確實(shí)應(yīng)該從A事務(wù)的read view中移除城瞎,但是因?yàn)閞ead view在可重復(fù)讀隔離級別下只會(huì)在第一條SQL執(zhí)行時(shí)創(chuàng)建一次,并始終保持不變直到事務(wù)結(jié)束脖镀。

**那么也就明白了步势,在可重復(fù)讀隔離級別下敛腌,因?yàn)閞ead view只在第一條SQL執(zhí)行時(shí)創(chuàng)建,因此并發(fā)訪問的其他事務(wù)提交前改動(dòng)的臟數(shù)據(jù)削祈、以及并發(fā)訪問的其他事務(wù)提交的改動(dòng)數(shù)據(jù)都對當(dāng)前事務(wù)是透明的(盡管確實(shí)是記錄在了undo log版本鏈中)**?脑漫,這就解決了臟讀和不可重復(fù)讀(即使其他事務(wù)提交的修改,對A事務(wù)來說前后查詢結(jié)果相同)的問題启昧!

### 讀已提交

還是借助上面事務(wù)處理的例子劈伴,所有的事務(wù)處理流程不變握爷,**只是將隔離級別調(diào)整為讀已提交新啼,讀已提交依舊遵守read view和undo log版本鏈機(jī)制,它和可重復(fù)讀級別的區(qū)別在于燥撞,每次執(zhí)行sql物舒,都會(huì)創(chuàng)建一個(gè)read view,獲取最新的事務(wù)快照火诸。**?而因?yàn)檫@個(gè)區(qū)別荠察,讀已提交產(chǎn)生了不可重復(fù)讀的問題,下面來分析一下原因:

![image.png](https://upload-images.jianshu.io/upload_images/27804186-75a7882aa7ca8911.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

事務(wù)A第一次查詢創(chuàng)建的read view:

`create_trx_id`?= 101|?`m_idx`?= [101, 102]|`min_trx_id`?= 101|`max_trx_id`?= 103

事務(wù)B的read view:

`create_trx_id`?= 102|?`m_idx`?= [101, 102]|`min_trx_id`?= 101|`max_trx_id`?= 103

事務(wù)A第二次查詢創(chuàng)建的read view:

`create_trx_id`?= 101|?`m_idx`?= [101]|`min_trx_id`?= 101|`max_trx_id`?= 103

(ps. 這里因?yàn)锳B事務(wù)是并發(fā)執(zhí)行盯荤,因此兩個(gè)事務(wù)創(chuàng)建的read view的max_trx_id = 103)

![image.png](https://upload-images.jianshu.io/upload_images/27804186-9e5da7c8ae524e80.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

這里重點(diǎn)觀察A事務(wù)的第二次查詢秋秤,之前你可能就意識(shí)到了,在事務(wù)B完成提交后航缀,當(dāng)前系統(tǒng)中活躍的事務(wù)id應(yīng)該移除102芥玉,但是因?yàn)?*在可重復(fù)讀隔離級別下,A事務(wù)的`read view`只會(huì)在第一個(gè)SQL執(zhí)行時(shí)創(chuàng)建赶袄,而在讀已提交隔離級別下抠藕,每次執(zhí)行SQL都會(huì)創(chuàng)建最新的read view**,且此時(shí)?`m_idx`數(shù)組中移除了102敬辣,那么事務(wù)A在追溯undo log版本鏈的時(shí)候零院,最新版本記錄的`trx_id = 102`,102不在A事務(wù)的m_idx數(shù)組中撰茎,且`101 = min_trx_id <= 102 < max_trx_id = 103`打洼,因此可以訪問到B事務(wù)的提交結(jié)果。

**那么對A事務(wù)來說炫惩,在事務(wù)過程中讀取同一條記錄第一次得到a=0阿浓,第二次得到a=1搔扁,所以出現(xiàn)了不可重復(fù)讀的問題(這里B不提交的話A如果就進(jìn)行了第二次查詢,則102不會(huì)從A事務(wù)的read view移除扭勉,則A事務(wù)依舊訪問不到B事務(wù)未提交的修改苛聘,因此臟讀還是可以避免的V揖邸)**

## 結(jié)束語

在我的理解中唱捣,MVCC多版本并發(fā)控制的實(shí)現(xiàn)可以理解成讀已提交震缭、可重復(fù)讀兩種隔離級別的實(shí)現(xiàn),通過控制read view的創(chuàng)建時(shí)機(jī)(其訪問機(jī)制是不變的)党涕,配合undo log版本鏈可以實(shí)現(xiàn)事務(wù)之間對同一條記錄的并發(fā)訪問巡社,并獲得不同的結(jié)果。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肥荔,一起剝皮案震驚了整個(gè)濱河市次企,隨后出現(xiàn)的幾起案子潜圃,更是在濱河造成了極大的恐慌舟茶,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異阀捅,居然都是意外死亡饲鄙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門帆谍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轴咱,“玉大人,你說我怎么就攤上這事窖剑∥魍粒” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵绘雁,是天一觀的道長援所。 經(jīng)常有香客問我,道長挪略,這世上最難降的妖魔是什么滔岳? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任谱煤,我火速辦了婚禮,結(jié)果婚禮上室叉,老公的妹妹穿的比我還像新娘硫惕。我一直安慰自己恼除,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布令野。 她就那樣靜靜地躺著徽级,像睡著了一般。 火紅的嫁衣襯著肌膚如雪堵幽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音殴胧,去河邊找鬼团滥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛灸姊,可吹牛的內(nèi)容都是我干的力惯。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼哮缺,長吁一口氣:“原來是場噩夢啊……” “哼尝苇!你這毒婦竟也來了埠胖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谊惭,沒想到半個(gè)月后侮东,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年众眨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沿腰。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颂龙,死狀恐怖纽什,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情企巢,我是刑警寧澤饺藤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布涕俗,位于F島的核電站,受9級特大地震影響萌抵,放射性物質(zhì)發(fā)生泄漏元镀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一讨永、第九天 我趴在偏房一處隱蔽的房頂上張望遇革。 院中可真熱鬧,春花似錦锻霎、人聲如沸揪漩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽囚痴。三九已至审葬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間痴荐,已是汗流浹背官册。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膝宁,地道東北人鸦难。 一個(gè)月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像员淫,于是被迫代替她去往敵國和親合蔽。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容