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