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