1皮获、MVCC
MVCC乓梨,全稱Multi-Version Concurrency Control牲剃,即多版本并發(fā)控制遣疯。MVCC是一種并發(fā)控制的方法,一般在數(shù)據(jù)庫管理系統(tǒng)中凿傅,實現(xiàn)對數(shù)據(jù)庫的并發(fā)訪問缠犀,在編程語言中實現(xiàn)事務(wù)內(nèi)存。
MVCC在MySQL InnoDB中的實現(xiàn)主要是為了提高數(shù)據(jù)庫并發(fā)性能聪舒,用更好的方式去處理讀寫沖突夭坪,做到即使有讀寫沖突時,也能做到不加鎖过椎,非阻塞并發(fā)讀。
2戏仓、當(dāng)前讀
像select lock in share mode(共享鎖), select for update ; update, insert ,delete(排他鎖)這些操作都是一種當(dāng)前讀疚宇,為什么叫當(dāng)前讀?就是它讀取的是記錄的最新版本赏殃,讀取時還要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄敷待,會對讀取的記錄進行加鎖。
3仁热、快照讀(提高數(shù)據(jù)庫的并發(fā)查詢能力)
像不加鎖的select操作就是快照讀榜揖,即不加鎖的非阻塞讀;快照讀的前提是隔離級別不是串行級別抗蠢,串行級別下的快照讀會退化成當(dāng)前讀举哟;之所以出現(xiàn)快照讀的情況,是基于提高并發(fā)性能的考慮迅矛,快照讀的實現(xiàn)是基于多版本并發(fā)控制妨猩,即MVCC,可以認為MVCC是行鎖的一個變種,但它在很多情況下秽褒,避免了加鎖操作壶硅,降低了開銷;既然是基于多版本销斟,即快照讀可能讀到的并不一定是數(shù)據(jù)的最新版本庐椒,而有可能是之前的歷史版本
4、當(dāng)前讀蚂踊、快照讀约谈、MVCC關(guān)系
MVCC多版本并發(fā)控制指的是維持一個數(shù)據(jù)的多個版本,使得讀寫操作沒有沖突,快照讀是MySQL為實現(xiàn)MVCC的一個非阻塞讀功能窗宇。MVCC模塊在MySQL中的具體實現(xiàn)是由三個隱式字段措伐,undo日志、read view三個組件來實現(xiàn)的军俊。
5侥加、MVCC解決的問題
數(shù)據(jù)庫并發(fā)場景有三種,分別為:
1粪躬、讀讀:不存在任何問題担败,也不需要并發(fā)控制
2、讀寫:有線程安全問題镰官,可能會造成事務(wù)隔離性問題提前,可能遇到臟讀、幻讀泳唠、不可重復(fù)讀
3狈网、寫寫:有線程安全問題,可能存在更新丟失問題
MVCC是一種用來解決讀寫沖突的無鎖并發(fā)控制笨腥,也就是為事務(wù)分配單項增長的時間戳拓哺,為每個修改保存一個版本,版本與事務(wù)時間戳關(guān)聯(lián)脖母,讀操作只讀該事務(wù)開始前的數(shù)據(jù)庫的快照士鸥,所以MVCC可以為數(shù)據(jù)庫解決以下問題:
1、在并發(fā)讀寫數(shù)據(jù)庫時谆级,可以做到在讀操作時不用阻塞寫操作烤礁,寫操作也不用阻塞讀操作,提高了數(shù)據(jù)庫并發(fā)讀寫的性能
2肥照、解決臟讀脚仔、幻讀、不可重復(fù)讀等事務(wù)隔離問題舆绎,但是不能解決更新丟失問題
6玻侥、MVCC實現(xiàn)原理
mvcc的實現(xiàn)原理主要依賴于記錄中的三個隱藏字段,undolog亿蒸,read view來實現(xiàn)的凑兰。
隱藏字段
每行記錄除了我們自定義的字段外,還有數(shù)據(jù)庫隱式定義的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
DB_TRX_ID
6字節(jié)边锁,最近修改事務(wù)id姑食,記錄創(chuàng)建這條記錄或者最后一次修改該記錄的事務(wù)id
DB_ROLL_PTR
7字節(jié),回滾指針茅坛,指向這條記錄的上一個版本,用于配合undolog音半,指向上一個舊版本
DB_ROW_ID
6字節(jié)则拷,隱藏的主鍵,如果數(shù)據(jù)表沒有主鍵曹鸠,那么innodb會自動生成一個6字節(jié)的row_id
記錄如圖所示:
在上圖中煌茬,DB_ROW_ID是數(shù)據(jù)庫默認為該行記錄生成的唯一隱式主鍵,DB_TRX_ID是當(dāng)前操作該記錄的事務(wù)ID彻桃,DB_ROLL_PTR是一個回滾指針坛善,用于配合undo日志,指向上一個舊版本
undo log
undolog被稱之為回滾日志邻眷,表示在進行insert眠屎,delete,update操作的時候產(chǎn)生的方便回滾的日志
當(dāng)進行insert操作的時候肆饶,產(chǎn)生的undolog只在事務(wù)回滾的時候需要改衩,并且在事務(wù)提交之后可以被立刻丟棄
當(dāng)進行update和delete操作的時候,產(chǎn)生的undolog不僅僅在事務(wù)回滾的時候需要驯镊,在快照讀的時候也需要葫督,所以不能隨便刪除,只有在快照讀或事務(wù)回滾不涉及該日志時板惑,對應(yīng)的日志才會被purge線程統(tǒng)一清除(當(dāng)數(shù)據(jù)發(fā)生更新和刪除操作的時候都只是設(shè)置一下老記錄的deleted_bit候衍,并不是真正的將過時的記錄刪除,因為為了節(jié)省磁盤空間洒放,innodb有專門的purge線程來清除deleted_bit為true的記錄,如果某個記錄的deleted_id為true滨砍,并且DB_TRX_ID相對于purge線程的read view 可見往湿,那么這條記錄一定時可以被清除的)
下面我們來看一下undolog生成的記錄鏈
1、假設(shè)有一個事務(wù)編號為1的事務(wù)向表中插入一條記錄惋戏,那么此時行數(shù)據(jù)的狀態(tài)為:
2领追、假設(shè)有第二個事務(wù)編號為2對該記錄的name做出修改,改為lisi
在事務(wù)2修改該行記錄數(shù)據(jù)時响逢,數(shù)據(jù)庫會對該行加排他鎖
然后把該行數(shù)據(jù)拷貝到undolog中绒窑,作為 舊記錄,即在undolog中有當(dāng)前行的拷貝副本
拷貝完畢后舔亭,修改該行name為lisi些膨,并且修改隱藏字段的事務(wù)id為當(dāng)前事務(wù)2的id,回滾指針指向拷貝到undolog的副本記錄中
事務(wù)提交后钦铺,釋放鎖
3订雾、假設(shè)有第三個事務(wù)編號為3對該記錄的age做了修改,改為32
在事務(wù)3修改該行數(shù)據(jù)的時矛洞,數(shù)據(jù)庫會對該行加排他鎖
然后把該行數(shù)據(jù)拷貝到undolog中洼哎,作為舊紀錄,發(fā)現(xiàn)該行記錄已經(jīng)有undolog了,那么最新的舊數(shù)據(jù)作為鏈表的表頭噩峦,插在該行記錄的undolog最前面
修改該行age為32歲锭沟,并且修改隱藏字段的事務(wù)id為當(dāng)前事務(wù)3的id,回滾指針指向剛剛拷貝的undolog的副本記錄
事務(wù)提交识补,釋放鎖
從上述的一系列圖中族淮,大家可以發(fā)現(xiàn),不同事務(wù)或者相同事務(wù)的對同一記錄的修改李请,會導(dǎo)致該記錄的undolog生成一條記錄版本線性表瞧筛,即鏈表,undolog的鏈首就是最新的舊記錄导盅,鏈尾就是最早的舊記錄较幌。
Read View
上面的流程如果看明白了,那么大家需要再深入理解下read view的概念了白翻。
Read View是事務(wù)進行快照讀操作的時候生產(chǎn)的讀視圖乍炉,在該事務(wù)執(zhí)行快照讀的那一刻,會生成一個數(shù)據(jù)系統(tǒng)當(dāng)前的快照滤馍,記錄并維護系統(tǒng)當(dāng)前活躍事務(wù)的id岛琼,事務(wù)的id值是遞增的。
其實Read View的最大作用是用來做可見性判斷的巢株,也就是說當(dāng)某個事務(wù)在執(zhí)行快照讀的時候槐瑞,對該記錄創(chuàng)建一個Read View的視圖,把它當(dāng)作條件去判斷當(dāng)前事務(wù)能夠看到哪個版本的數(shù)據(jù)阁苞,有可能讀取到的是最新的數(shù)據(jù)困檩,也有可能讀取的是當(dāng)前行記錄的undolog中某個版本的數(shù)據(jù)
Read View遵循的可見性算法主要是將要被修改的數(shù)據(jù)的最新記錄中的DB_TRX_ID(當(dāng)前事務(wù)id)取出來,與系統(tǒng)當(dāng)前其他活躍事務(wù)的id去對比那槽,如果DB_TRX_ID跟Read View的屬性做了比較悼沿,不符合可見性,那么就通過DB_ROLL_PTR回滾指針去取出undolog中的DB_TRX_ID做比較骚灸,即遍歷鏈表中的DB_TRX_ID糟趾,直到找到滿足條件的DB_TRX_ID,這個DB_TRX_ID所在的舊記錄就是當(dāng)前事務(wù)能看到的最新老版本數(shù)據(jù)。
Read View的可見性規(guī)則如下所示:
首先要知道Read View中的三個全局屬性:
trx_list:一個數(shù)值列表甚牲,用來維護Read View生成時刻系統(tǒng)正活躍的事務(wù)ID(1,2,3)
up_limit_id:記錄trx_list列表中事務(wù)ID最小的ID(1)
low_limit_id:Read View生成時刻系統(tǒng)尚未分配的下一個事務(wù)ID义郑,(4)
具體的比較規(guī)則如下:
1、首先比較DB_TRX_ID < up_limit_id,如果小于丈钙,則當(dāng)前事務(wù)能看到DB_TRX_ID所在的記錄魔慷,如果大于等于進入下一個判斷
2、接下來判斷DB_TRX_ID >= low_limit_id,如果大于等于則代表DB_TRX_ID所在的記錄在Read View生成后才出現(xiàn)的著恩,那么對于當(dāng)前事務(wù)肯定不可見院尔,如果小于蜻展,則進入下一步判斷
3、判斷DB_TRX_ID是否在活躍事務(wù)中邀摆,如果在纵顾,則代表在Read View生成時刻,這個事務(wù)還是活躍狀態(tài)栋盹,還沒有commit施逾,修改的數(shù)據(jù),當(dāng)前事務(wù)也是看不到例获,如果不在汉额,則說明這個事務(wù)在Read View生成之前就已經(jīng)開始commit,那么修改的結(jié)果是能夠看見的榨汤。
7蠕搜、MVCC的整體處理流程
從上述表格中,我們可以看到收壕,當(dāng)事務(wù)2對某行數(shù)據(jù)執(zhí)行了快照讀妓灌,數(shù)據(jù)庫為該行數(shù)據(jù)生成一個Read View視圖,可以看到事務(wù)1和事務(wù)3還在活躍狀態(tài)蜜宪,事務(wù)4在事務(wù)2快照讀的前一刻提交了更新虫埂,所以,在Read View中記錄了系統(tǒng)當(dāng)前活躍事務(wù)1圃验,3掉伏,維護在一個列表中。同時可以看到up_limit_id的值為1澳窑,而low_limit_id為5斧散,如下圖所示:
在上述的例子中,只有事務(wù)4修改過該行記錄照捡,并在事務(wù)2進行快照讀前,就提交了事務(wù)话侧,所以該行當(dāng)前數(shù)據(jù)的undolog如下所示:
當(dāng)事務(wù)2在快照讀該行記錄的是栗精,會拿著該行記錄的DB_TRX_ID去跟up_limit_id,lower_limit_id和活躍事務(wù)列表進行比較,判讀事務(wù)2能看到該行記錄的版本是哪個瞻鹏。
具體流程如下:先拿該行記錄的事務(wù)ID(4)去跟Read View中的up_limit_id相比較悲立,判斷是否小于,通過對比發(fā)現(xiàn)不小于新博,所以不符合條件薪夕,繼續(xù)判斷4是否大于等于low_limit_id,通過比較發(fā)現(xiàn)也不大于,所以不符合條件赫悄,判斷事務(wù)4是否處理trx_list列表中原献,發(fā)現(xiàn)不再次列表中馏慨,那么符合可見性條件,所以事務(wù)4修改后提交的最新結(jié)果對事務(wù)2 的快照是可見的姑隅,因此写隶,事務(wù)2讀取到的最新數(shù)據(jù)記錄是事務(wù)4所提交的版本,而事務(wù)4提交的版本也是全局角度的最新版本讲仰。如下圖所示:
當(dāng)上述的內(nèi)容都看明白了的話慕趴,那么大家就應(yīng)該能夠搞清楚這幾個核心概念之間的關(guān)系了,下面我們講一個不同的隔離級別下的快照讀的不同鄙陡。
8冕房、RC、RR級別下的InnoDB快照讀有什么不同
因為Read View生成時機的不同趁矾,從而造成RC耙册、RR級別下快照讀的結(jié)果的不同
1、在RR級別下的某個事務(wù)的對某條記錄的第一次快照讀會創(chuàng)建一個快照即Read View,將當(dāng)前系統(tǒng)活躍的其他事務(wù)記錄起來愈魏,此后在調(diào)用快照讀的時候觅玻,還是使用的是同一個Read View,所以只要當(dāng)前事務(wù)在其他事務(wù)提交更新之前使用過快照讀,那么之后的快照讀使用的都是同一個Read View,所以對之后的修改不可見
2培漏、在RR級別下溪厘,快照讀生成Read View時,Read View會記錄此時所有其他活動和事務(wù)的快照牌柄,這些事務(wù)的修改對于當(dāng)前事務(wù)都是不可見的畸悬,而早于Read View創(chuàng)建的事務(wù)所做的修改均是可見
3、在RC級別下珊佣,事務(wù)中蹋宦,每次快照讀都會新生成一個快照和Read View,這就是我們在RC級別下的事務(wù)中可以看到別的事務(wù)提交的更新的原因。
總結(jié):在RC隔離級別下咒锻,是每個快照讀都會生成并獲取最新的Read View,而在RR隔離級別下冷冗,則是同一個事務(wù)中的第一個快照讀才會創(chuàng)建Read View,之后的快照讀獲取的都是同一個Read View.