Mvcc 學習筆記

Leaving things better than you found them

MVCC 筆記


MVCC為了解決什么問題?

多版本并發控制,針對在并發訪問數據庫時對于數據版本的控制以及隔離性問題,Mysql使用了MVCC的思路來進行版本控制

MVCC的MYSQL 實現淺析?

Mysql 的 MVCC實現大致是通過隱藏列中的DB_ROLL_PTR字段以及undo log的方式生成數據版本鏈,在創建事務時生成ReadView來進行版本比對,從而篩選出當前事務可見的數據行

事務并發執行會遇到的問題?

臟寫(Dirty Write):一個事務修改了另一個事務未提交過的數據

臟讀(Dirty Read):一個事務讀取到了另一個事務未提交過的數據

不可重復讀(Non-Repeatable Read):一個事務只能讀取到另一個已經提交的事務修改過的數據,并且其他事務每對該數據進行一次修改并提交后,該事物都能查到最新的值(在一個事務中的多次查詢,可以查詢到多個其他事務提交的最新值)

幻讀(Phantom):一個事務根據某些條件查詢出一些記錄之后,另一個事務又向表中插入了符合這些條件的記錄,原先的事務用相同的條件再次查詢時,能把另外一個事務插入的數據也查詢出來

按問題嚴重性排序:

臟寫 > 臟讀 > 不可重復讀 > 幻讀

標準的四種 SQL事務隔離級別(并非Mysql定義)

Read UnCommittd:未提交讀

Read Committd:已提交讀

Repeatable Read:可重復讀

Serializable:可串行化

隔離級別 臟讀 不可重復讀 幻讀
Read UnCommittd Possible Possible Possible
Read Committd Not Possible Possible Possible
Repeatable Read Not Possible Not Possible Possible
serializable Not Possible Not Possible Not Possible

也就是說:

Read UnCommittd 隔離級別下,可能發生臟讀不可重復讀幻讀問題

Read Committd 隔離級別下,可能發生不可重復讀幻讀問題

Repeatable Read 隔離級別下,可能會發生幻讀但是在Mysql中,Repeatable Read隔離級別可以處理幻讀的問題

serializable 隔離級別下,各種問題都不會發生

至于臟寫,應為臟寫實在太嚴重了,所以無論哪個隔離級別都不允許臟寫的情況發生。

什么是版本鏈 ? undo日志 ?

undo日志:用于記錄事務中未提交的變更記錄,主要用于保證事務的原子性,任何對數據的操作都會記錄到undo日志中,直到提交事務或者rollback,才會進行清理。

說起版本鏈,我們得先有行格式的概念,我們大概看一下Compact格式下所看到的一行數據的格式:

行格式.png

在一個正常的行信息中,除了記錄了用戶的真實記錄以外,innoDB還會為每條記錄都添加2個隱藏列以及一個可選列

列名 是否必須 占用空間 描述
DB_TRX_ID 6字節 事務ID
DB_ROLL_PTR 7字節 回滾指針
DB_ROW_ID 6字節 行id,唯一標識一條記錄

DB_ROW_ID 在沒有自定義主鍵以及存在非Null的Unique鍵時才會添加該列

這里來簡單闡述一下DB_TRX_ID以及DB_ROLL_PTR在版本鏈中的作用

DB_TRX_ID:每次一個事務對某條聚簇索引記錄進行改動時,都會把該事務的事務id賦值給該記錄的DB_TRX_ID隱藏列,注意事務id是遞增的。

DB_ROLL_PTR:每次對某條聚簇索引記錄改動時,都會將舊的版本寫入到 undo日志中,然后然后這個隱藏列就相當于一個指針,可以通過它來找到該記錄修改前的信息。

注意insert是不會產生DB_ROLL_PRT的,因為insert時并沒有更早的版本存在

了解到這里我們大概就能看到版本鏈的雛形了,也就是利用了DB_ROLL_PTR來鏈接上一個版本的數據;

我們以一個hero表為例:


hero table.png

假如我們有一個hero表其中number為1的記錄name初始化為劉備,我們執行如下兩個語句:

hero 事務.png

它的版本鏈大概就是下面這個樣子:


version list1.png

每次對該記錄更新后,都會將舊值放到 undo 日志中,隨著更新次數的增多,所有版本都會被DB_ROLL_PRT屬性鏈接成為一個鏈表,我們把這個鏈表稱之為版本鏈,版本鏈的頭節點就是當前記錄最新的值,另外,每個版本中還包含生成該版本時對應的事務id

ReadView 是什么?

ReadView 可以按字面意思理解為讀視圖,也就是在事務開始時生成的一個快照,ReadView的設計主要是為了解決 "判斷版本鏈中哪個版本是當前事務可見" 的問題

SERIALIZABLE隔離級別采用加鎖的方式來訪問記錄,而READ COMMITTEDREPEATABLE READ隔離級別在事務的不同階段會創建ReadView

Read committed 隔離級別下,每次讀取數據前都會生產一個ReadView

Repeatable Read 隔離級別下,在第一次讀取數據時生產一個ReadView

關于兩種隔離級別下產生ReadView時機不同帶來的影響,后面描述

ReadView 主要組成結構:

m_ids:表示在生成ReadView時當前系統中活躍的讀寫事務的事務id列表

min_trx_id:表示在生成ReadView時當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中最小的值

max_trx_id:表示生成ReadView時系統中應該分配給下一個事務的事務id

  max_trx_id并非是是m_ids中的最大值,事務id是遞增分配的,比方說現在有id為1,2,3這三個事務,之后id為3的事務提交了,那么一個新的讀事務在生產ReadView時,m_ids時就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4

creator_trx_id:表示生成該ReadView的事務的事務id

  只有在對表中的記錄做改動時(執行Insert、update、delete)才會為事務分配事務id,否則在一個只讀事務中,事務id都默認為0

當生成了這個ReadView,這樣在訪問某條記錄時,只需按照下邊的步驟判斷記錄的某個版本是否可見(可見性要求):

  • 如果被訪問版本的 trx_id 屬性值與 ReadView 中的 creator_trx_id 值相同,意味著當前事務在訪問他自己修改過的記錄,所以該版本可以被當前事務訪問

  • 如果被訪問版本的trx_id屬性值小于ReadView中的min_trx_id值,表明生成該版本的事務在當前事務生成ReadView前已經提交,所以該版本可以被當前事務訪問

  • 如果被訪問版本的trx_id屬性值大于ReadView中的max_trx_id值,表明生成該版本的事務在當前事務生成ReadView后才開啟,所以該版本不可以被當前事務訪問

  • 如果被訪問版本的trx_id屬性值在ReadViewmin_trx_idmax_trx_id之間,那就需要判斷一下trx_id屬性是不是在m_ids列表中,如果在,說明創建ReadView時生成該版本的事務還是活躍的,該版本不可以被訪問,如果不在,說明創建ReadView時生成該版本的事務已經被提交,該版本可以被訪問

Read Committd 每次讀取數據前都生成一個ReadView

假如現在系統中有兩個事務在執行,事務id分別是100200

trx 2.png

版本鏈如下:


version list2.png

假設現在有一個使用Read Committd隔離級別的事務開始執行:

trx read committed.png

那么這個Select1的執行過程如下:

  1. 在執行SELECT 語句時會現生成一個ReadViewReadViewm_ids列表內容為[100,200],min_trx_id為100,max_trx_id為201,creator_trx_id為0

  2. 然后從版本鏈中挑選可見記錄,從圖中可以看出,最新版本的列name的內容是 '張飛',該版本的事務id為100,在m_ids內,所以不符合可見性要求,根據DB_ROLL_PTR(roll_pointer)找到下一個版本

  3. 下一個版本的列name的值為 '關羽',該數據的事務id也為100,在m_ids范圍內,不符合可見性要求,繼續跳到下一個版本

  4. 下一個版本的列name的值為 '劉備',該版本的事務id為80,小于ReadView中的min_trx_id值100,所以這個版本符合可見性要求,最終返回給用戶的就是這條name列為 '劉備' 的數據

之后,我們把事務id100的事務提交一下:

commit transaction.png

然后再到事務id為200的事務中更新一下hero表中number為1的數據:


update trx 200.png

此刻表hero中number為1的記錄的版本鏈如下:


version list3.png

然后我們再到剛才使用Read Committd隔離級別的事務中繼續查找這個number為1的記錄,如下:

SELECT 2.png

其中的Select2的執行過程如下:

  1. 在執行SELECT2 語句時又會單獨生成一個ReadView,該ReadViewm_ids列表內容是[200](事務id為100的那個事務已經提交了,所以再次生成快照時就沒有它了),min_trx_id為200,max_trx_id為201,creator_id為0

  2. 然后從版本鏈中挑選可見的記錄,從圖中可以看出,最新版本的列name值為 '諸葛亮',該版本的事務id為200,在m_ids列表內,所以不符合可見性要求,根據DB_ROLL_PTR(roll_pointer)跳到下一個版本。

  3. 下一個版本的列name的值為 '趙云',該版本的事務id為200,在m_ids列表內,所以也不符合要求,繼續跳到下一個版本

  4. 下一個版本的列name的值為 '張飛',該版本的事務id為100,小于ReadViewmin_trx_id的值200,所以符合要求,最后返回給用戶的版本就是這條列name為 '張飛' 的記錄

可以看到在Read Committd的隔離級別下,出現了不可重復讀的場景

Read Committd隔離級別下,事務在每次查詢開始時都會創建一個獨立的ReadView,關于Repeatable Read隔離級別下版本鏈以及執行過程大概類似這里就不闡述了(歡迎討論),只是在Repeatable Read隔離級別下,在事務中多次讀數據時,只會在第一次讀取數據時創建ReadView,后面的查詢都會復用第一次創建的ReadView,這就保證了前后兩次查詢到的結果一致,可以嘗試使用Repeatable Read隔離級別的特性去看看上面的版本鏈,select2在Repeatable Read級別下應該返回什么?怎么去理解可重復度?


總結:

從上邊的描述中我們可以看出來,所謂的MVCC(Multi-Version Concurrency Control ,多版本并發控制)指的就是在使用Read CommittdRepeatable Read這兩種隔離級別的事務在執行普通的SEELCT操作時訪問記錄的版本鏈的過程,這樣子可以使不同事務的讀-寫、寫-讀操作并發執行,從而提升系統性能。Read CommittdRepeatable Read這兩個隔離級別的一個很大不同就是:生成ReadView的時機不同,Read Committd在每一次進行普通SELECT操作前都會生成一個ReadView,而Repeatable Read只在第一次進行普通SELECT操作前生成一個ReadView,之后的查詢操作都重復使用這個ReadView就好了。

疑問?

undo log理論上會在事務提交后進行刪除,那么版本鏈如何形成呢?

實際 insert undo 在事務提交之后就可以被釋放了,update undo由于還需要支持MVCC,不能立即刪除掉,實際在行結構中除了隱藏列還有一個delete mark的標記位,1代表刪除,0代表未刪除,用來記錄數據是否被刪除,所以在上面的版本鏈判斷數據時并非是簡單的判斷事務id,同時還會考慮這個delete_mark標記,同時在mysql中,作者為了減少因為移除數據后的磁盤重新排列的性能問題,還搞了一個所謂的垃圾鏈表,在這個鏈表中的記錄占用的空間稱之為所謂的可重用空間,之后如果有新記錄要插入到表中的話,可能會把這些被刪除記錄占用的存儲空間給覆蓋掉,當然也并非所有被標記了刪除都數據都是覆蓋處理,這里就涉及到mysql的后臺的purge線程的作用了,后面再去了解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容

  • 事務的四大特性(簡稱ACID) 數據庫如果支持事務的操作,那么就具備以下四個特性: 1、原子性(Atomicity...
    gregoriusxu閱讀 324評論 0 0
  • 事務就是要保證一組數據庫操作,要么全部成功,要么全部失敗。在 MySQL 中,事務支持是在引擎層實現的。MySQL...
    itczl閱讀 1,010評論 0 0
  • 【2018-03-03】開學同聽“第一課” 今天是我第一次和孩子做同學,同窗同聽,親子共修,感覺棒棒噠。回眸一整天...
    林文斌閱讀 242評論 0 0
  • 百度“等價交換”:等價交換是不同效用的商品按照它們各自具有的價格相交換。是商品交換的一般原則,是不同使用價值...
    愛冰淇淋的兔子閱讀 1,108評論 0 1
  • 文/ 麥醺 大學畢業后,我漂在西北的一個三線城市里。當時剛上班的我和同事合租在一個50平...
    麥小醺閱讀 786評論 2 3