您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“MVCC實(shí)現(xiàn)原理是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
MVCC即多版本控制器,其特點(diǎn)就是在同一時(shí)間,不同事務(wù)可以讀取到不同版本的數(shù)據(jù),從而去解決臟讀和不可重復(fù)讀的問題。
這樣的解釋你看了不下幾十遍了吧!但是你真的理解什么是多版本控制器嗎?
生活案例:搬家
最近小Q跟自己的女朋友搬到新家,由于出小區(qū)的時(shí)候需要支付當(dāng)月的物業(yè)費(fèi)。
于是小Q跟自己的女朋友同時(shí)登錄了小區(qū)提供的物業(yè)繳費(fèi)系統(tǒng)。
悲觀并發(fā)控制
假設(shè)小Q正在查當(dāng)月需要繳納的費(fèi)用是多少進(jìn)行支付的時(shí)候,此時(shí)小Q查詢的這條數(shù)據(jù)是已經(jīng)被鎖定的。
那么小Q女朋友是無法訪問該數(shù)據(jù)的,直至小Q支付完成或者退出系統(tǒng)將悲觀鎖釋放,小Q的女朋友才可以查詢到數(shù)據(jù)。
悲觀鎖保證在同一時(shí)間只能有一個(gè)線程訪問,默認(rèn)數(shù)據(jù)在訪問的時(shí)候會(huì)產(chǎn)生沖突,然后在整個(gè)過程都加上了鎖。
這樣的系統(tǒng)對(duì)于用戶來說就是毫無體驗(yàn)感,如果多個(gè)人同時(shí)需要訪問一條信息,只能在一臺(tái)設(shè)備上看嘍!
樂觀并發(fā)控制
在小Q查看物業(yè)費(fèi)欠費(fèi)情況,并且支付的同時(shí),小Q的女朋友也可以訪問到該數(shù)據(jù)。
樂觀鎖認(rèn)為即使在并發(fā)環(huán)境下,也不會(huì)產(chǎn)生沖突問題,所以不會(huì)去做加鎖操作。
而是在數(shù)據(jù)提交的時(shí)候進(jìn)行檢測(cè),如果發(fā)現(xiàn)有沖突則返回沖突信息。
小結(jié)
Innodb的MVCC機(jī)制就是樂觀鎖的一種體現(xiàn),讀不加鎖,讀寫不沖突,在不加鎖的情況下能讓多個(gè)事務(wù)進(jìn)行并發(fā)讀寫,并且解決讀寫沖突問題,極大的提高系統(tǒng)的并發(fā)性
鎖按照粒度分為表鎖、行鎖、頁(yè)鎖。
按照使用方式分為共享鎖、排它鎖。
根據(jù)思想分為樂觀鎖、悲觀鎖。
無論是樂觀鎖、悲觀鎖都只是一種思想而已,并不是實(shí)際的鎖機(jī)制,這點(diǎn)一定要清楚。
悲觀鎖實(shí)際為悲觀并發(fā)控制,縮寫PCC。
悲觀鎖持消極態(tài)度,認(rèn)為每一次訪問數(shù)據(jù)時(shí),總是會(huì)發(fā)生沖突,因此,每次訪問必須先鎖住數(shù)據(jù),完成訪問后在釋放鎖。
保證在同一時(shí)間只有單個(gè)線程可以訪問,實(shí)現(xiàn)數(shù)據(jù)的排它性。同時(shí)悲觀鎖使用數(shù)據(jù)庫(kù)自身的鎖機(jī)制實(shí)現(xiàn),可以解決讀-寫,寫-寫的沖突。
那么在什么場(chǎng)景下可以使用悲觀鎖呢!
悲觀鎖適用于在寫多讀少的并發(fā)環(huán)境下使用,雖然并發(fā)效率不高,但是保證了數(shù)據(jù)的安全性。
跟悲觀鎖一樣,樂觀鎖實(shí)際為樂觀并發(fā)控制,縮寫為OCC。
樂觀鎖相對(duì)于悲觀鎖而言,認(rèn)為即使在并發(fā)環(huán)境下,外界對(duì)數(shù)據(jù)的操作不會(huì)產(chǎn)生沖突,所以不會(huì)去加鎖,而是會(huì)在提交更新的時(shí)候才會(huì)正式的對(duì)數(shù)據(jù)沖突與否進(jìn)行檢測(cè)。
如果發(fā)現(xiàn)沖突,要么再重試一次,要么切換為悲觀的策略。
樂觀并發(fā)控制要解決的是數(shù)據(jù)庫(kù)并發(fā)場(chǎng)景下的寫-寫沖突
,指用無鎖的方式去解決
在事務(wù)并發(fā)的情況下會(huì)產(chǎn)生以下問題。
臟讀:讀取其它事務(wù)未提交的數(shù)據(jù)。
不可重復(fù)讀:一個(gè)事務(wù)在讀取一條數(shù)據(jù)時(shí),由于另一個(gè)事務(wù)修改了這條數(shù)據(jù)并且提交事務(wù),再次讀取時(shí)導(dǎo)致數(shù)據(jù)不一致
幻讀:一個(gè)事務(wù)讀取了某個(gè)范圍的數(shù)據(jù),同時(shí)另一個(gè)事務(wù)新增了這個(gè)范圍的數(shù)據(jù),再次讀取發(fā)現(xiàn)倆次得到的結(jié)果不一致。
MVCC在Innodb存儲(chǔ)引擎的實(shí)現(xiàn)主要是為了提高數(shù)據(jù)庫(kù)并發(fā)能力,用更好的方式去處理讀--寫沖突,同時(shí)做到不加鎖、非阻塞并發(fā)讀寫。
mvcc可以解決臟讀,不可重復(fù)讀,mvcc使用快照讀解決了部分幻讀問題,但是在修改時(shí)還是使用當(dāng)前讀,所以還是存在幻讀問題,幻讀問題最終就是使用間隙鎖解決。
在了解MVCC是如何解決事務(wù)并發(fā)帶來的問題之前,需要先明白倆個(gè)概念,當(dāng)前讀、快照讀。
給讀操作加上共享鎖、排它鎖,DML操作加上排它鎖,這些操作就是當(dāng)前讀。
共享鎖、排它鎖也被稱之為讀鎖、寫鎖。
共享鎖與共享鎖是共存的,但是要修改、添加、刪除時(shí),必須等到共享鎖釋放才可進(jìn)行操作。
因?yàn)樵贗nnodb存儲(chǔ)引擎中,DML操作都會(huì)隱式添加排它鎖。
所以說當(dāng)前讀所讀取的記錄就是最新的記錄,讀取數(shù)據(jù)時(shí)加上鎖,保證其它事務(wù)不能修改當(dāng)前記錄。
如果你看到這里就默認(rèn)你對(duì)隔離級(jí)別有一定的了解哈!
快照讀的前提是隔離級(jí)別不是串行級(jí)別,串行級(jí)別的快照讀會(huì)退化成當(dāng)前讀。
快照讀的出現(xiàn)旨在提高事務(wù)并發(fā)性,其實(shí)現(xiàn)基于本文的主角MVCC即多版本控制器。
MVCC可以認(rèn)為是行鎖的一個(gè)變種,但是它在很多情況下避免了加鎖操作。
所以說快照讀的數(shù)據(jù)有可能不是最新的,而是之前版本的數(shù)據(jù)。
為什么要提到快照讀呢!因?yàn)閞ead-view就是通過快照讀生成的,為了防止后文概念模糊,所以在這里進(jìn)行說明。
不加鎖的簡(jiǎn)單的select都屬于快照讀。
select id name user where id = 1;
與之對(duì)應(yīng)的則是當(dāng)前讀,給select加上共享鎖、排它鎖。
select id name from user where id = 1 lock in share mode; select id name from user where id = 1 for update;
終于來到本文最重要的部分,前邊的敘述都是為了給原理這一塊做鋪墊。
在這之前需要知道MVCC只在REPEATABLE READ(可重復(fù)讀) 和 READ COMMITTED(已讀提交)
這倆種隔離級(jí)別下適用。
MVCC實(shí)現(xiàn)原理是由倆個(gè)隱式字段、undo日志、Read view來實(shí)現(xiàn)的。
在Innodb存儲(chǔ)引擎中,在有聚簇索引的情況下每一行記錄中都會(huì)隱藏倆個(gè)字段,如果沒有聚簇索引則還有一個(gè)6byte的隱藏主鍵。
這倆個(gè)隱藏列一個(gè)記錄的是何時(shí)被創(chuàng)建的,一個(gè)記錄的是什么時(shí)候被刪除。
這里不要理解為是記錄的是時(shí)間,存儲(chǔ)的是事務(wù)ID。
倆個(gè)隱式字段為DB_TRX_ID,DB_ROLL_PTR,沒有聚簇索引還會(huì)有DB_ROW_ID這個(gè)字段。
DB_TRX_ID:記錄創(chuàng)建這條數(shù)據(jù)上次修改它的事務(wù) ID
DB_ROLL_PTR:回滾指針,指向這條記錄的上一個(gè)版本
隱式字段實(shí)際還有一個(gè)delete flag字段,即記錄被更新或刪除,這里的刪除并不代表真的刪除,而是將這條記錄的delete flag改為true(這里埋下一個(gè)伏筆,數(shù)據(jù)庫(kù)的刪除是真的刪除嗎?)
之前對(duì)undo log的作用只提到了回滾操作實(shí)現(xiàn)原子性,現(xiàn)在需要知道的另一個(gè)作用就是實(shí)現(xiàn)MVCC多版本控制器。
undo log細(xì)分為倆種,insert時(shí)產(chǎn)生的undo log、update,delete時(shí)產(chǎn)生的undo log
在Innodb中insert產(chǎn)生的undo log在提交事務(wù)之后就會(huì)被刪除,因?yàn)樾虏迦氲臄?shù)據(jù)沒有歷史版本,所以無需維護(hù)undo log。
update和delete操作產(chǎn)生的undo log都屬于一種類型,在事務(wù)回滾時(shí)需要,而且在快照讀時(shí)也需要,則需要維護(hù)多個(gè)版本信息。只有在快照讀和事務(wù)回滾不涉及該日志時(shí),對(duì)應(yīng)的日志才會(huì)被purge
線程統(tǒng)一刪除。
purge線程會(huì)清理undo log的歷史版本,同樣也會(huì)清理del flag標(biāo)記的記錄。
undo log在mvcc中的作用
寫到這里關(guān)于undo log在mvcc中的作用估計(jì)還是蒙圈的。
undo log保存的是一個(gè)版本鏈,也就是使用DB_ROLL_PTR這個(gè)字段來連接的。
當(dāng)數(shù)據(jù)庫(kù)執(zhí)行一個(gè)select語句時(shí)會(huì)產(chǎn)生一致性視圖read view
。
那么這個(gè)read view是由查詢時(shí)所有未提交事務(wù)ID組成的數(shù)組,數(shù)組中最小的事務(wù)ID為min_id和已創(chuàng)建的最大事務(wù)ID為max_id組成,查詢的數(shù)據(jù)結(jié)果需要跟read-view做比較從而得到快照結(jié)果。
所以說undo log在mvcc中的作用就是為了根據(jù)存儲(chǔ)的事務(wù)ID和一致性視圖做對(duì)比,從而得到快照結(jié)果。
假設(shè)一開始的數(shù)據(jù)為下圖
此時(shí)執(zhí)行了一條更新的SQL語句update user set name = 'niuniu where id = 1'
,那么undo log的記錄就會(huì)發(fā)生變化
也就是說當(dāng)執(zhí)行一條更新語句時(shí)會(huì)把之前的原有數(shù)據(jù)拷貝到undo log日志中。
同時(shí)你可以看見最新的一條記錄在末尾處連接了一條線,也就是說DB_ROLL_PTR
記錄的就是存放在undo log日志的指針地址。
最終有可能需要通過指針來找到歷史數(shù)據(jù)。
當(dāng)執(zhí)行SQL語句查詢時(shí)會(huì)產(chǎn)生一致性視圖,也就是read-view,它是由查詢的那一時(shí)間所有未提交事務(wù)ID組成的數(shù)組,和已經(jīng)創(chuàng)建的最大事務(wù)ID組成的。
在這個(gè)數(shù)組中最小的事務(wù)ID被稱之為min_id,最大事務(wù)ID被稱之為max_id,查詢的數(shù)據(jù)結(jié)果要根據(jù)read-view做對(duì)比從而得到快照結(jié)果。
于是就產(chǎn)生了以下的對(duì)比規(guī)則,這個(gè)規(guī)則就是使用當(dāng)前的記錄的trx_id跟read-view進(jìn)行對(duì)比,對(duì)比規(guī)則如下。
如果落在trx_id<min_id,表示此版本是已經(jīng)提交的事務(wù)生成的,由于事務(wù)已經(jīng)提交所以數(shù)據(jù)是可見的
如果落在trx_id>max_id,表示此版本是由將來啟動(dòng)的事務(wù)生成的,是肯定不可見的
若在min_id<=trx_id<=max_id時(shí)
如果row的trx_id在數(shù)組中,表示此版本是由還沒提交的事務(wù)生成的,不可見,但是當(dāng)前自己的事務(wù)是可見的
如果row的trx_id不在數(shù)組中,表明是提交的事務(wù)生成了該版本,可見
在這里還有一個(gè)特殊情況那就是對(duì)于已經(jīng)刪除的數(shù)據(jù),在之前的undo log日志講述時(shí)說了update和delete是同一種類型的undo log,同樣也可以認(rèn)為delete就是update的特殊情況。
當(dāng)刪除一條數(shù)據(jù)時(shí)會(huì)將版本鏈上最新的數(shù)據(jù)復(fù)制一份,然后將trx_id修改為刪除時(shí)的trx_id,同時(shí)在該記錄的頭信息中存在一個(gè)delete flag標(biāo)記,將這個(gè)標(biāo)記寫上true,用來表示當(dāng)前記錄已經(jīng)刪除。
在查詢時(shí)按照版本鏈的規(guī)則查詢到對(duì)應(yīng)的記錄,如果delete flag標(biāo)記位為true,意味著數(shù)據(jù)已經(jīng)被刪除,則不返回?cái)?shù)據(jù)。
如果你對(duì)這里的read-view的生成和版本鏈對(duì)比規(guī)則不懂,不要著急,也不要在這里浪費(fèi)時(shí)間,請(qǐng)繼續(xù)往下看,咔咔會(huì)使用一個(gè)簡(jiǎn)單的案例和一個(gè)復(fù)雜的案例給大家重現(xiàn)上述的規(guī)則。
下圖是準(zhǔn)備的素材,這里應(yīng)該都理解select 返回的結(jié)果為niuniu,即事務(wù)102修改后的結(jié)果
從上圖中可以看到有三個(gè)事務(wù)正在進(jìn)行。
事務(wù)ID為100、101是修改的其它表,只有事務(wù)ID為102修改的需要查詢的這張表。
接下來看看select這一列查詢返回的結(jié)果是不是就是事務(wù)ID為102修改的結(jié)果。
此時(shí)生成的read-view為[100,101],102
那么現(xiàn)在就可以返回去看一下read-view規(guī)則,在這里事務(wù)ID100就是min_id,事務(wù)ID102就是max_id。
這個(gè) select語句返回結(jié)果肯定是 niuniu。
那么接下來看一下在MVCC中是如何查找數(shù)據(jù)的。
當(dāng)前版本鏈。
那么就會(huì)拿著trx_id 為102進(jìn)行比對(duì),會(huì)發(fā)現(xiàn)這個(gè)102就是max_id
然后你再看一下版本鏈的對(duì)比規(guī)則中第三種情況
如果落在min_id<=trx_id<=max_id會(huì)存在倆種情況
此時(shí)信息就已經(jīng)非常明確了,事務(wù)ID102是沒有在數(shù)組中的,所以表示這個(gè)版本是已經(jīng)提交的事務(wù)生成的,那么就是可見的唄!
毫無疑問查詢會(huì)返回niuniu這個(gè)值
先通過這個(gè)簡(jiǎn)單的案例讓你對(duì)版本鏈有一個(gè)簡(jiǎn)單的理解,接下來將使用一個(gè)比較繁瑣的案例再來跟大家演示一遍。
本例要求知道 select的第二個(gè)查詢結(jié)果。深黑色字體。
同樣是在kaka那一條記錄的基礎(chǔ)上。
當(dāng)事務(wù)ID100兩次更新后,版本鏈也會(huì)改變,現(xiàn)在的版本鏈如下圖,紅色部分為最新數(shù)據(jù),藍(lán)色數(shù)據(jù)為undo log的版本鏈數(shù)據(jù)。
對(duì)于此時(shí)生成的read-view你會(huì)有什么疑問,在RR級(jí)別也就是可重復(fù)讀的隔離級(jí)別下。
當(dāng)在一個(gè)事務(wù)下執(zhí)行查詢時(shí),所有的read-view都是沿用的第一條查詢語句生成的。
那此時(shí)的read-view也就是[100,101],102
看一下底層查找步驟
目前數(shù)據(jù)的事務(wù)ID為100
根據(jù)規(guī)則會(huì)落在min_id<=trx_id<=max_id這個(gè)區(qū)間
并且當(dāng)前行的事務(wù)ID100是在read-view的數(shù)組中的,表示此時(shí)事務(wù)還沒有提交則不可見
繼續(xù)在版本鏈中往下尋找,此時(shí)找到的事務(wù)ID還是100,跟上述流程一致
通過查找版本鏈,將發(fā)現(xiàn)事務(wù) ID為102
102是read-view的max_id,同樣也會(huì)落在min_id<=trx_id<=max_id這個(gè)區(qū)間,但是跟之前不同的是事務(wù)102是沒有在數(shù)組中的,表示這個(gè)版本事務(wù)已經(jīng)提交了所以是可見的
最后返回的是 niuniu
為了讓大家體驗(yàn)一下可重復(fù)讀級(jí)別生成的read-view是根據(jù)在同一事務(wù)中第一條快照讀產(chǎn)生的,再來看一個(gè)案例
此時(shí)的事務(wù)ID101也再對(duì)數(shù)據(jù)更新兩次,然后在進(jìn)行查詢看一下會(huì)返回什么值
經(jīng)過案例一、案例二的熟悉現(xiàn)在對(duì)undo log的版本鏈和對(duì)比規(guī)則已經(jīng)有了一定的了解了吧!
案例三就不在那么詳細(xì)的說明了。
此時(shí)的版本鏈如下
此時(shí)的read-view依然為[100,101],102。
那么首先會(huì)根據(jù)事務(wù)101去版本鏈對(duì)比,事務(wù)101和事務(wù)100都會(huì)落在min_id<=trx_id<=max_id
這個(gè)區(qū)間,并且還都在數(shù)組中,所以數(shù)據(jù)是不可見的。
那么繼續(xù)往版本鏈中尋找就會(huì)遇到事務(wù)102,這個(gè)是最大的事務(wù)ID并且不在數(shù)組中,所以是可見的。
于是最終的返回結(jié)果還是niuniu
。
可以看到個(gè)案例三的圖不同的是新增了一個(gè)查詢語句,那么假設(shè)這倆條語句執(zhí)行的時(shí)間都是一致的,它們返回的結(jié)果會(huì)相同嗎?
案例三查詢到的值為niuniu
其實(shí)現(xiàn)在版本鏈跟案例三也是一致的
那么來梳理一下尋找過程
首先這里的read-view發(fā)生了變化,此時(shí)的read-view為[101],102
拿著當(dāng)前的事務(wù)ID101跟版本鏈規(guī)則進(jìn)行對(duì)比,落盤在min_id<=trx_id<=max_id,并且在數(shù)組中,則數(shù)據(jù)不可見
然后進(jìn)入版本鏈,找到下一個(gè)數(shù)據(jù)的事務(wù) ID,還是101,與上一個(gè)一致
接下來是事務(wù)ID100
事務(wù)ID100是落在trx_id<min_id,表示此版本是已經(jīng)提交的事務(wù)生成的,由于事務(wù)已經(jīng)提交所以數(shù)據(jù)是可見的
所以最終返回結(jié)果為niuniu2
在同一個(gè)事務(wù)中進(jìn)行查詢,會(huì)沿用第一次查詢語句生成的read-view(前提是隔離級(jí)別是在可重復(fù)讀)
通過以上的四個(gè)案例,在版本鏈尋找過程中,可以總結(jié)出一個(gè)小技巧
根據(jù)這個(gè)小技巧你可以很快的得知此版本是否可見。
如果當(dāng)前的事務(wù)ID在綠色部分,是已經(jīng)提交事務(wù),則數(shù)據(jù)可見
如果當(dāng)前的事務(wù)ID在藍(lán)色部分,會(huì)有倆種情況,如果當(dāng)前事務(wù)ID在read-view數(shù)組內(nèi),是沒有提交的事務(wù)不可見,如果不在數(shù)組內(nèi)數(shù)據(jù)可見
如果落在紅色部分,則不考慮,對(duì)于未來的事情不去想即可。
“MVCC實(shí)現(xiàn)原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。