MySQL技術(shù)中MVCC多版本并發(fā)的示例分析

這篇文章將為大家詳細講解有關(guān)MySQL技術(shù)中MVCC多版本并發(fā)的示例分析,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

創(chuàng)新互聯(lián)建站-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價比高陵網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式高陵網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋高陵地區(qū)。費用合理售后完善,10多年實體公司更值得信賴。

一.前提概要

什么是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ā)讀。反正每次修改都會在版本鏈中記錄。SELECT可以去版本鏈中拿記錄,這就實現(xiàn)了讀-寫,寫-讀的并發(fā)執(zhí)行,提升了系統(tǒng)的性能


已提交讀(READ COMMITTD)和可重復(fù)讀(REPEATABLE READ)這兩種隔離級別下的事務(wù)對于SELECT操作會訪問版本鏈中的記錄的過程。


什么是當(dāng)前讀和快照讀

MySQL InnoDB下的當(dāng)前讀和快照讀?

當(dāng)前讀

select lock in share mode(共享鎖), select for update ; update, insert ,delete(排他鎖)這些操作都是一種當(dāng)前讀,就是它讀取的是記錄的最新版本,讀取時還要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄,會對讀取的記錄進行加鎖。


快照讀

像不加鎖的select操作就是快照讀,即不加鎖的非阻塞讀;快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當(dāng)前讀之所以出現(xiàn)快照讀的情況,是基于提高并發(fā)性能的考慮,快照讀的實現(xiàn)是基于多版本并發(fā)控制,即MVCC,可以認為MVCC是行鎖的一個變種,但它在很多情況下,避免了加鎖操作,降低了開銷;既然是基于多版本,即快照讀可能讀到的并不一定是數(shù)據(jù)的最新版本,而有可能是之前的歷史版本。


MVCC就是為了實現(xiàn)讀-寫沖突不加鎖,而這個讀指的就是快照讀,非當(dāng)前讀,當(dāng)前讀實際上是一種加鎖的操作,是悲觀鎖的實現(xiàn)


當(dāng)前讀,快照讀和MVCC的關(guān)系

MVCC多版本并發(fā)控制指的是 “維持一個數(shù)據(jù)的多個版本,使得讀寫操作沒有沖突” 這么一個概念。僅僅是一個理想概念 而在MySQL中,實現(xiàn)這么一個MVCC理想概念,我們就需要MySQL提供具體的功能去實現(xiàn)它,而快照讀就是MySQL為我們實現(xiàn)MVCC理想模型的其中一個具體非阻塞讀功能。


而相對而言,當(dāng)前讀就是悲觀鎖的具體功能實現(xiàn),要說的再細致一些,快照讀本身也是一個抽象概念,再深入研究。MVCC模型在MySQL中的具體實現(xiàn)則是由 3個隱式字段,undo日志 ,Read View等去完成的,具體可以看下面的MVCC實現(xiàn)原理


MVCC能解決什么問題,好處是?

數(shù)據(jù)庫并發(fā)場景有三種,分別為:

  • 讀-讀:不存在任何問題,也不需要并發(fā)控制

  • 讀-寫:有線程安全問題,可能會造成事務(wù)隔離性問題,可能遇到臟讀,幻讀,不可重復(fù)讀

  • 寫-寫:有線程安全問題,可能會存在更新丟失問題,比如第一類更新丟失,第二類更新丟失

MVCC帶來的好處是?

多版本并發(fā)控制(MVCC)是一種用來解決讀-寫沖突的無鎖并發(fā)控制,也就是為事務(wù)分配單向增長的時間戳,為每個修改保存一個版本,版本與事務(wù)時間戳關(guān)聯(lián),讀操作只讀該事務(wù)開始前的數(shù)據(jù)庫的快照。 所以MVCC可以為數(shù)據(jù)庫解決以下問題;

在并發(fā)讀寫數(shù)據(jù)庫時,可以做到在讀操作時不用阻塞寫操作,寫操作也不用阻塞讀操作,提高了數(shù)據(jù)庫并發(fā)讀寫的性能,同時還可以解決臟讀,幻讀,不可重復(fù)讀等事務(wù)隔離問題,但不能解決更新丟失問題


總之,MVCC就是因為大牛們,不滿意只讓數(shù)據(jù)庫采用悲觀鎖這樣性能不佳的形式去解決讀-寫沖突問題,而提出的解決方案,所以在數(shù)據(jù)庫中,因為有了MVCC,所以我們可以形成兩個組合:

  • MVCC + 悲觀鎖: MVCC解決讀寫沖突,悲觀鎖解決寫寫沖突

  • MVCC + 樂觀鎖: MVCC解決讀寫沖突,樂觀鎖解決寫寫沖突

這種組合的方式就可以最大程度的提高數(shù)據(jù)庫并發(fā)性能,并解決讀寫沖突,和寫寫沖突導(dǎo)致的問題

MVCC的實現(xiàn)原理

MVCC的目的就是多版本并發(fā)控制,在數(shù)據(jù)庫中的實現(xiàn),就是為了解決讀寫沖突,它的實現(xiàn)原理主要是依賴記錄中的 3個隱式字段,undo日志 ,Read View 來實現(xiàn)的。所以我們先來看看這個三個point的概念

隱式字段

每行記錄除了我們自定義的字段外,還有數(shù)據(jù)庫隱式定義的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

  • DB_TRX_ID:6byte,最近修改(修改/插入)事務(wù)ID:記錄創(chuàng)建這條記錄/最后一次修改該記錄的事務(wù)ID,這個事務(wù)ID用來存儲的每次對某條聚簇索引記錄進行修改的時候的事務(wù)ID。

  • DB_ROLL_PTR:7byte,回滾指針,指向這條記錄的上一個版本(存儲于rollback segment里)-> 目標(biāo)為undoLog指針每次對哪條聚簇索引記錄有修改的時候,都會把老版本寫入undo日志中。這個roll_pointer就是存了一個指針,它指向這條聚簇索引記錄的上一個版本的位置,通過它來獲得上一個版本的記錄信息。(注意插入操作的undo日志沒有這個屬性,因為它沒有老版本)

  • DB_ROW_ID:6byte,隱含的自增ID(隱藏主鍵),如果數(shù)據(jù)表沒有主鍵,InnoDB會自動以DB_ROW_ID產(chǎn)生一個聚簇索引

  • 實際還有一個刪除flag隱藏字段, 既記錄被更新或刪除并不代表真的刪除,而是刪除flag變了

MySQL技術(shù)中MVCC多版本并發(fā)的示例分析


  • DB_ROW_ID是數(shù)據(jù)庫默認為該行記錄生成的唯一隱式主鍵

  • DB_TRX_ID是當(dāng)前操作該記錄的事務(wù)ID;

  • DB_ROLL_PTR是一個回滾指針,用于配合undo日志,指向上一個舊版本


undo日志

undo log主要分為兩種:

insert undo log

代表事務(wù)在insert新記錄時產(chǎn)生的undo log, 只在事務(wù)回滾時需要,并且在事務(wù)提交后可以被立即丟棄。

update undo log

代表 事務(wù)在進行update或delete時產(chǎn)生的undo log; 不僅在事務(wù)回滾時需要,在快照讀時也需要;所以不能隨便刪除,只有在快照讀或事務(wù)回滾不涉及該日志時,對應(yīng)的日志才會被purge線程統(tǒng)一清除

purge操作

 為了實現(xiàn)InnoDB的MVCC機制,更新或者刪除操作都只是設(shè)置一下老記錄的deleted_bit,并不真正將過時的記錄刪除。

節(jié)省磁盤空間,InnoDB有專門的purge線程來清理deleted_bit為true的記錄。為了不影響MVCC的正常工作,purge線程自己也維護了一個read view(這個read view相當(dāng)于系統(tǒng)中最老活躍事務(wù)的read view); 如果某個記錄的deleted_bit為true,并且DB_TRX_ID相對于purge線程的read view可見,那么這條記錄一定是可以被安全清除的。

對MVCC有幫助的實質(zhì)是update undo log,undo log實際上就是存在rollback segment中舊記錄鏈,它的執(zhí)行流程如下:

一、比如一個有個事務(wù)插入persion表插入了一條新記錄,記錄如下,name為Jerry, age為24歲,隱式主鍵是1,事務(wù)ID和回滾指針,我們假設(shè)為NULL

MySQL技術(shù)中MVCC多版本并發(fā)的示例分析

二、現(xiàn)在來了一個事務(wù)1對該記錄的name做出了修改,改為Tom


  • 在事務(wù)1修改該行(記錄)數(shù)據(jù)時,數(shù)據(jù)庫會先對該行加排他鎖

  • 然后把該行數(shù)據(jù)拷貝到undo log中,作為舊記錄,既在undo log中有當(dāng)前行的拷貝副本

  • 拷貝完畢后,修改該行name為Tom,并且修改隱藏字段的事務(wù)ID為當(dāng)前事務(wù)1的ID, 我們默認從1開始,之后遞增,回滾指針指向拷貝到undo log的副本記錄,既表示我的上一個版本就是它

  • 事務(wù)提交后,釋放鎖

MySQL技術(shù)中MVCC多版本并發(fā)的示例分析

三、 又來了個事務(wù)2修改person表的同一個記錄,將age修改為30歲

  • 在事務(wù)2修改該行數(shù)據(jù)時,數(shù)據(jù)庫也先為該行加鎖

  • 然后把該行數(shù)據(jù)拷貝到undo log中,作為舊記錄,發(fā)現(xiàn)該行記錄已經(jīng)有undo log了,那么最新的舊數(shù)據(jù)作為鏈表的表頭,插在該行記錄的undo log最前面

  • 修改該行age為30歲,并且修改隱藏字段的事務(wù)ID為當(dāng)前事務(wù)2的ID, 那就是2,回滾指針指向剛剛拷貝到undo log的副本記錄

  • 事務(wù)提交,釋放鎖

MySQL技術(shù)中MVCC多版本并發(fā)的示例分析

從上面,我們就可以看出,不同事務(wù)或者相同事務(wù)的對同一記錄的修改,會導(dǎo)致該記錄的undo log成為一條記錄版本線性表,既鏈表,undo log的鏈首就是最新的舊記錄,鏈尾就是最早的舊記錄(當(dāng)然就像之前說的該undo log的節(jié)點可能是會purge線程清除掉,向圖中的第一條insert undo log,其實在事務(wù)提交之后可能就被刪除丟失了,不過這里為了演示,所以還放在這里)


Read View(讀視圖)

什么是Read View?

已提交讀和可重復(fù)讀的區(qū)別就在于它們生成ReadView的策略不同。

什么是Read View,說白了Read View就是事務(wù)進行快照讀操作的時候生產(chǎn)的讀視圖(Read View),在該事務(wù)執(zhí)行的快照讀的那一刻,會生成數(shù)據(jù)庫系統(tǒng)當(dāng)前的一個快照,記錄并維護系統(tǒng)當(dāng)前活躍事務(wù)的ID(當(dāng)每個事務(wù)開啟時,都會被分配一個ID, 這個ID是遞增的,所以最新的事務(wù),ID值越大)

所以我們知道 Read View主要是用來做可見性判斷的, 即當(dāng)我們某個事務(wù)執(zhí)行快照讀的時候,對該記錄創(chuàng)建一個Read View讀視圖,把它比作條件用來判斷當(dāng)前事務(wù)能夠看到哪個版本的數(shù)據(jù),既可能是當(dāng)前最新的數(shù)據(jù),也有可能是該行記錄的undo log里面的某個版本的數(shù)據(jù)。


Read View遵循一個可見性算法,主要是將要被修改的數(shù)據(jù)的最新記錄中的DB_TRX_ID(即當(dāng)前事務(wù)ID)取出來,與系統(tǒng)當(dāng)前其他活躍事務(wù)的ID去對比(由Read View維護),如果DB_TRX_ID跟Read View的屬性做了某些比較,不符合可見性,通過DB_ROLL_PTR回滾指針去取出Undo Log中的DB_TRX_ID再比較,即遍歷鏈表的DB_TRX_ID(從鏈首到鏈尾,即從最近的一次修改查起),直到找到滿足特定條件的DB_TRX_ID, 那么這個DB_TRX_ID所在的舊記錄就是當(dāng)前事務(wù)能看見的最新老版本。

那么這個判斷條件是什么呢?

MySQL技術(shù)中MVCC多版本并發(fā)的示例分析

如上,它是一段MySQL判斷可見性的一段源碼,即changes_visible方法(不完全哈,但能看出大致邏輯),該方法展示了我們拿DB_TRX_ID去跟Read View某些屬性進行怎么樣的比較


在展示之前,我先簡化一下Read View,我們可以把Read View簡單的理解成有三個全局屬性


trx_list(名字我隨便取的)

一個數(shù)值列表,用來維護Read View生成時刻系統(tǒng)正活躍的事務(wù)ID

up_limit_id

記錄trx_list列表中事務(wù)ID最小的ID

low_limit_id

ReadView生成時刻系統(tǒng)尚未分配的下一個事務(wù)ID,也就是目前已出現(xiàn)過的事務(wù)ID的最大值+1


  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ù)之中,trx_list.contains(DB_TRX_ID),如果在,則代表我Read View生成時刻,你這個事務(wù)還在活躍,還沒有Commit,你修改的數(shù)據(jù),我當(dāng)前事務(wù)也是看不見的; 4. 如果不在,則說明,你這個事務(wù)在Read View生成之前就已經(jīng)Commit了,你修改的結(jié)果,我當(dāng)前事務(wù)是能看見的。

整體流程

了解隱式字段,undo log, 以及Read View的概念之后,就可以來看看MVCC實現(xiàn)的整體流程是怎么樣了

當(dāng)事務(wù)2對某行數(shù)據(jù)執(zhí)行了快照讀,數(shù)據(jù)庫為該行數(shù)據(jù)生成一個Read View讀視圖,假設(shè)當(dāng)前事務(wù)ID為2,此時還有事務(wù)1和事務(wù)3在活躍中,事務(wù)4在事務(wù)2快照讀前一刻提交更新了,所以Read View記錄了系統(tǒng)當(dāng)前活躍事務(wù)1,3的ID,維護在一個列表上,假設(shè)我們稱trx_list


MySQL技術(shù)中MVCC多版本并發(fā)的示例分析

Read View不僅僅會通過一個列表trx_list來維護事務(wù)2執(zhí)行快照讀那刻系統(tǒng)正活躍的事務(wù)ID,還會有兩個屬性up_limit_id(記錄trx_list列表中事務(wù)ID最小的ID),low_limit_id(記錄trx_list列表中事務(wù)ID最大的ID,也有人說快照讀那刻系統(tǒng)尚未分配的下一個事務(wù)ID也就是目前已出現(xiàn)過的事務(wù)ID的最大值+1,我更傾向于后者;所以在這里例子中up_limit_id就是1,low_limit_id就是4 + 1 = 5,trx_list集合的值是1,3,Read View如下圖

MySQL技術(shù)中MVCC多版本并發(fā)的示例分析

例子中,只有事務(wù)4修改過該行記錄,并在事務(wù)2執(zhí)行快照讀前,就提交了事務(wù),所以當(dāng)前該行當(dāng)前數(shù)據(jù)的undo log如下圖所示;我們的事務(wù)2在快照讀該行記錄的時候,就會拿該行記錄的DB_TRX_ID去跟up_limit_id,low_limit_id和活躍事務(wù)ID列表(trx_list)進行比較,判斷當(dāng)前事務(wù)2能看到該記錄的版本是哪個。

MySQL技術(shù)中MVCC多版本并發(fā)的示例分析


所以先拿該記錄DB_TRX_ID字段記錄的事務(wù)ID4去跟Read View的的up_limit_id比較,看4是否小于up_limit_id(1),所以不符合條件,繼續(xù)判斷 4 是否大于等于 low_limit_id(5),也不符合條件,最后判斷4是否處于trx_list中的活躍事務(wù), 最后發(fā)現(xiàn)事務(wù)ID為4的事務(wù)不在當(dāng)前活躍事務(wù)列表中, 符合可見性條件,所以事務(wù)4修改后提交的最新結(jié)果對事務(wù)2快照讀時是可見的,所以事務(wù)2能讀到的最新數(shù)據(jù)記錄是事務(wù)4所提交的版本,而事務(wù)4提交的版本也是全局角度上最新的版本。MySQL技術(shù)中MVCC多版本并發(fā)的示例分析 也正是Read View生成時機的不同,從而造成RC,RR級別下快照讀的結(jié)果的不同。


MVCC相關(guān)問題

RR是如何在RC級的基礎(chǔ)上解決不可重復(fù)讀的? 當(dāng)前讀和快照讀在RR級別下的區(qū)別: MySQL技術(shù)中MVCC多版本并發(fā)的示例分析


MySQL技術(shù)中MVCC多版本并發(fā)的示例分析

而在表2這里的順序中,事務(wù)B在事務(wù)A提交后的快照讀和當(dāng)前讀都是實時的新數(shù)據(jù)400,這是為什么呢?

這里與上表的唯一區(qū)別僅僅是表1的事務(wù)B在事務(wù)A修改金額前快照讀過一次金額數(shù)據(jù),而表2的事務(wù)B在事務(wù)A修改金額前沒有進行過快照讀。

所以我們知道事務(wù)中快照讀的結(jié)果是非常依賴該事務(wù)首次出現(xiàn)快照讀的地方,即某個事務(wù)中首次出現(xiàn)快照讀的地方非常關(guān)鍵,它有決定該事務(wù)后續(xù)快照讀結(jié)果的能力。我們這里測試的是更新,同時刪除和更新也是一樣的,如果事務(wù)B的快照讀是在事務(wù)A操作之后進行的,事務(wù)B的快照讀也是能讀取到最新的數(shù)據(jù)的

RC,RR級別下的InnoDB快照讀有什么不同?
  1. 正是Read View生成時機的不同,從而造成RC,RR級別下快照讀的結(jié)果的不同。

  2. 在RR級別下的某個事務(wù)的對某條記錄的第一次快照讀會創(chuàng)建一個快照及Read View, 將當(dāng)前系統(tǒng)活躍的其他事務(wù)記錄起來,此后在調(diào)用快照讀的時候,還是使用的是同一個Read View,所以只要當(dāng)前事務(wù)在其他事務(wù)提交更新之前使用過快照讀,那么之后的快照讀使用的都是同一個Read View,所以對之后的修改不可見;

  3. 即RR級別下,快照讀生成Read View時,Read View會記錄此時所有其他活動事務(wù)的快照,這些事務(wù)的修改對于當(dāng)前事務(wù)都是不可見的。而早于Read View創(chuàng)建的事務(wù)所做的修改均是可見,而在RC級別下的,事務(wù)中,每次快照讀都會新生成一個快照和Read View, 這就是我們在RC級別下的事務(wù)中可以看到別的事務(wù)提交的更新的原因

  4. 總之在RC隔離級別下,是每個快照讀都會生成并獲取最新的Read View;而在RR隔離級別下,則是同一個事務(wù)中的第一個快照讀才會創(chuàng)建Read View, 之后的快照讀獲取的都是同一個Read View。

關(guān)于MySQL技術(shù)中MVCC多版本并發(fā)的示例分析就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

網(wǎng)站欄目:MySQL技術(shù)中MVCC多版本并發(fā)的示例分析
新聞來源:http://muchs.cn/article14/jehpde.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供電子商務(wù)、網(wǎng)站營銷、網(wǎng)站策劃、網(wǎng)站導(dǎo)航全網(wǎng)營銷推廣、面包屑導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁設(shè)計