亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

MySQL多版本并發(fā)控制MVCC詳解

 更新時間:2022年07月25日 17:16:54   作者:艷梓  
這篇文章主要介紹了MySQL多版本并發(fā)控制MVCC詳解,MVCC是通過數(shù)據(jù)行的多個版本管理來實現(xiàn)數(shù)據(jù)庫的并發(fā)控制,這項技術(shù)使得在InnoDB的事務(wù)隔離級別下執(zhí)行一致性讀操作有了保證

1.什么是MVCC

MVCC (Multiversion Concurrency Control),多版本并發(fā)控制。顧名思義,MVCC是通過數(shù)據(jù)行的多個版本管理來實現(xiàn)數(shù)據(jù)庫的并發(fā)控制。這項技術(shù)使得在InnoDB的事務(wù)隔離級別下執(zhí)行一致性讀.操作有了保證。換言之,就是為了查詢一些正在被另一個事務(wù)更新的行,并且可以看到它們被更新之前的值,這樣在做查詢的時候就不用等待另一個事務(wù)釋放鎖。

MVCC沒有正式的標(biāo)準(zhǔn),在不同的DBMS中MVCC的實現(xiàn)方式可能是不同的,也不是普遍使用的(大家可以參考相關(guān)的DBMS文檔)。這里講解InnoDB中 MVCC的實現(xiàn)機制(MySQL其它的存儲引擎并不支持它)

2快照讀與當(dāng)前讀

MVCC在MySQL InnoDB中的實現(xiàn)主要是為了提高數(shù)據(jù)庫并發(fā)性能,用更好的方式去處理讀-寫沖突,做到即使有讀寫沖突時,也能做到不加鎖,非阻塞并發(fā)讀,而這個讀指的就是快照讀,而非當(dāng)前讀。當(dāng)前讀實際上是一種加鎖的操作,是悲觀鎖的實現(xiàn)。而MVCC本質(zhì)是采用樂觀鎖思想的一種方式。

2.1 快照讀

快照讀又叫一致性讀,讀取的是快照數(shù)據(jù)。不加鎖的簡單的SELECT都屬于快照讀,即不加鎖的非阻塞讀;比如這樣:

select * from player where ...

之所以出現(xiàn)快照讀的情況,是基于提高并發(fā)性能的考慮,快照讀的實現(xiàn)是基于MVCC,它在很多情況下,避免了加鎖操作,降低了開銷。

既然是基于多版本,那么快照讀可能讀到的并不一定是數(shù)據(jù)的最新版本,而有可能是之前的歷史版本。

快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當(dāng)前讀。

2.2當(dāng)前讀

當(dāng)前讀讀取的是記錄的最新版本(最新數(shù)據(jù),而不是歷史版本的數(shù)據(jù)),讀取時還要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄,會對讀取的記錄進行加鎖。加鎖的SELECT,或者對數(shù)據(jù)進行增刪改都會進行當(dāng)前讀。比如:

3.復(fù)習(xí)

3.1 再談隔離級別

我們知道事務(wù)有4個隔離級別,可能存在三種并發(fā)問題:

在MysQL 中,默認的隔離級別是可重復(fù)讀,可以解決臟讀和不可重復(fù)讀的問題,如果僅從定義的角度來看,它并不能解決幻讀問題。如果我們想要解決幻讀問題,就需要采用串行化的方式,也就是將隔離級別提升到最高,但這樣一來就會大幅降低數(shù)據(jù)庫的事務(wù)并發(fā)能力。

MVCC可以不采用鎖機制,而是通過樂觀鎖的方式來解決不可重復(fù)讀和幻讀問題!它可以在大多數(shù)情況下替代行級鎖,降低系統(tǒng)的開銷。

3.2 隱藏字段、Undo Log版本鏈

回顧一下undo日志的版本鏈,對于使用InnoDB存儲引擎的表來說,它的聚簇索引記錄中都包含兩個必要的隱藏列。

trx_id:每次一個事務(wù)對某條聚簇索引記錄進行改動時,都會把該事務(wù)的事務(wù)id賦值給trx_id隱藏列。roll_pointer:每次對某條聚簇索引記錄進行改動時,都會把舊的版本寫入到undo日志中,然后這個隱藏列就相當(dāng)于一個指針,可以通過它來找到該記錄修改前的信息。

4、MVCC實現(xiàn)原理之ReadView

MVCC的實現(xiàn)依賴于:隱藏字段、Undo Log、Read View

4.1什么是ReadView

在MVCC機制中,多個事務(wù)對同一個行記錄進行更新會產(chǎn)生多個歷史快照,這些歷史快照保存在Undo Log里。如果一個事務(wù)想要查詢這個行記錄,需要讀取哪個版本的行記錄呢?這時就需要用到ReadView了,它幫我們解決了行的可見性問題。

ReadView就是事務(wù)在使用MVCC機制進行快照讀操作時產(chǎn)生的讀視圖。當(dāng)事務(wù)啟動時,會生成數(shù)據(jù)庫系統(tǒng)當(dāng)前的一個快照,InnoDB為每個事務(wù)構(gòu)造了一個數(shù)組,用來記錄并維護系統(tǒng)當(dāng)前活躍事務(wù)的lD(“"活躍"指的就是,啟動了但還沒提交)。

4.2 設(shè)計思路

使用READ UNCOMMITTED隔離級別的事務(wù),由于可以讀到未提交事務(wù)修改過的記錄,所以直接讀取記錄的最新版本就好了。

使用SERIALIZABLE隔離級別的事務(wù),InnoDB規(guī)定使用加鎖的方式來訪問記錄。

使用READ COMMITTEDREPEATABLE READ隔離級別的事務(wù),都必須保證讀到已經(jīng)提交了的事務(wù)修改過的記錄。假如另一個事務(wù)已經(jīng)修改了記錄但是尚未提交,是不能直接讀取最新版本的記錄的,核心問題就是需要判斷一下版本鏈中的哪個版本是當(dāng)前事務(wù)可見的,這是ReadView要解決的主要問題。

這個ReadView中主要包含4個比較重要的內(nèi)容,分別如下:

4.3 ReadView的規(guī)則

有了這個ReadView,這樣在訪問某條記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見。

  • 如果被訪問版本的trx_id屬性值與ReadView中的creator_trx_id值相同,意味著當(dāng)前事務(wù)在訪問它自己修改過的記錄,所以該版本可以被當(dāng)前事務(wù)訪問。
  • 如果被訪問版本的trx_id屬性值小于ReadView中的up_limit_id值,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成ReadView前已經(jīng)提交,所以該版本可以被當(dāng)前事務(wù)訪問。
  • 如果被訪問版本的trx_id屬性值大于或等于ReadView中的low_limit_id值,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成ReadView后才開啟,所以該版本不可以被當(dāng)前事務(wù)訪問。
  • 如果被訪問版本的trx_id屬性值在ReadView的up_limit_idlow_limit_id之間,那就需要判斷一下trx_id屬性值是不是在trx_ids列表中。如果在,說明創(chuàng)建ReadView時生成該版本的事務(wù)還是活躍的,該版本不可以被訪問。如果不在,說明創(chuàng)建ReadView時生成該版本的事務(wù)已經(jīng)被提交,該版本可以被訪問。 4.4 MVCC整體操作流程

了解了這些概念之后,我們來看下當(dāng)查詢一條記錄的時候,系統(tǒng)如何通過MVCC找到它:

  • 1.首先獲取事務(wù)自己的版本號,也就是事務(wù)ID;
  • 2.獲取ReadView;
  • 3.查詢得到的數(shù)據(jù),然后與ReadView中的事務(wù)版本號進行比較;
  • 4.如果不符合Readview規(guī)則,就需要從Undo Log中獲取歷史快照;
  • 5.最后返回符合規(guī)則的數(shù)據(jù)。

如果某個版本的數(shù)據(jù)對當(dāng)前事務(wù)不可見的話,那就順著版本鏈找到下一個版本的數(shù)據(jù),繼續(xù)按照上邊的步驟判斷可見性,依此類推,直到版本鏈中的最后一個版本。如果最后一個版本也不可見的話,那么就意味著該條記錄對該事務(wù)完全不可見,查詢結(jié)果就不包含該記錄。

InnoDB中,MVCC是通過Undo Log + Read View進行數(shù)據(jù)讀取,Undo Log保存了歷史快照,而Read View規(guī)則幫我們判斷當(dāng)前版本的數(shù)據(jù)是否可見。

在隔離級別為讀已提交(Read Committed)時,一個事務(wù)中的每一次select查詢都會重新獲取一次Read View。

當(dāng)隔離級別為可重讀的時候,就避免了不可重復(fù)讀,這是因為一個事務(wù)只在第一次select的時候會獲取一次Read View,而后面所有的select都會復(fù)用這個Read View,

如下所示:

5.舉例說明

假設(shè)現(xiàn)在student表中只有一條由事務(wù)id8事務(wù)插入一條記錄:

MVCC只能在READ COMMITTED和REPEATABLE READ兩個隔離級別下工作。接下來看一下READ COMMITTEDREPEATABLE READ所謂的生成Readview的時機不同到底不同在哪里。

5.1 READ COMMITTED

隔離級別下:

READ COMMITTED:每次讀取數(shù)據(jù)前都生成一個ReadView

現(xiàn)在有兩個事務(wù)id分別為10、20的事務(wù)在執(zhí)行:

說明:事務(wù)執(zhí)行過程中,只有在第一次真正修改記錄時(比如使用INSERT、DELETE、UPDATE語句),才會被分配一個單獨的事務(wù)id,這個事務(wù)id是遞增的。所以我們才在事務(wù)2中更新一些別的表的記錄,目的是讓它分配事務(wù)id。

此刻,表student中id為1的記錄得到的版本鏈表如下所示:

假設(shè)現(xiàn)在有一個使用READ COMMITED隔離級別的事務(wù)開始執(zhí)行:

這個SELECT1的執(zhí)行過程如下:

步驟1∶在執(zhí)行SELECT語句時會先生成一個ReadView ,ReadView的trx_ids列表的內(nèi)容就是[10,20],up_limit_id10, low_limit_id21 , creator_trx_id0
步驟2:從版本鏈中挑選可見的記錄,從圖中看出,最新版本的列name的內(nèi)容是'王五',該版本的trx_id值為10,在trx_ids列表內(nèi),所以不符合可見性要求,根據(jù)roll_pointer跳到下一個版本。
步驟3:下一個版本的列name的內(nèi)容是'李四',該版本的trx_id值也為10,也在trx_ids列表內(nèi),所以也不符合要求,繼續(xù)跳到下一個版本。
步驟4:下一個版本的列 name的內(nèi)容是‘張三',該版本的trx_id值為8,小于ReadView 中的up_limit_id值10,所以這個版本是符合要求的,最后返回給用戶的版本就是這條列name為‘張三’的記錄。

之后,我們把事務(wù)id10的事務(wù)提交一下:

然后再到事務(wù)id20的事務(wù)中更新一下表studentid1的記錄:

此刻,表student中id為1的記錄的版本鏈就長這樣:

然后再到剛才使用READ COMMITTED隔離級別的事務(wù)中繼續(xù)查找id為1的記錄,如下:

這個SELECT2的執(zhí)行過程如下:

步驟1∶在執(zhí)行SELECT語句時會又會單獨生成一個ReadView,該ReadView的trx_ids列表的內(nèi)容就是[20],up_limit_id為20,low_limit_id為21, creator_trx_id為0。
步驟2:從版本鏈中挑選可見的記錄,從圖中看出,最新版本的列name的內(nèi)容是‘宋八’,該版本的tr×_id值為20,在trx_ids列表內(nèi),所以不符合可見性要求,根據(jù)roll.pointer跳到下一個版本。
步驟3∶下一個版本的列name的內(nèi)容是’錢七’,該版本的trx_id值為20,也在trx_ids列表內(nèi),所以也不符合要求,繼續(xù)跳到下一個版本。
步驟4∶下一個版本的列name的內(nèi)容是’王五’,該版本的trx_id值為10,小于ReadView中的up_limit.id值20,所以這個版本是符合要求的,最后返回給用戶的版本就是這條列name為’王五’的記錄。
以此類推,如果之后事務(wù)id為20的記錄也提交了,再次在使用READ CONMMITTED隔離級別的事務(wù)中查詢表student中id值為1的記錄時,得到的結(jié)果就是‘宋八’了,具體流程我們就不分析了。

5.2 REPEATABLE READ

隔離級別下:

使用REPEATABLE READ隔離級別的事務(wù)來說,只會在第一次執(zhí)行查詢語句時生成一個ReadView,之后的查詢就不會重復(fù)生成了。

比如,系統(tǒng)里有兩個事務(wù)id分別為10、20的事務(wù)在執(zhí)行:

此刻,表student中id為1的記錄得到的版本鏈表如下所示:

假設(shè)現(xiàn)在有一個使用REPEATABLE READ隔離級別的事務(wù)開始執(zhí)行:

此時執(zhí)行過程與read committed相同

然后再到剛才使用REPEATABLE READ隔離級別的事務(wù)中繼續(xù)查找id為1的記錄,如下:

這個SELECT2的執(zhí)行過程如下:

步驟1:因為當(dāng)前事務(wù)的隔離級別為REPEATABLE READ,而之前在執(zhí)行SELECT1時已經(jīng)生成過ReadView了,所以此時直接復(fù)用之前的ReadView,之前的ReadView的trx_ids列表的內(nèi)容就是[10,20],up_limit_id為10, low_limit_id為21 , creator_trx_id為0。
步驟2:然后從版本鏈中挑選可見的記錄,從圖中可以看出,最新版本的列name的內(nèi)容是’宋八’trx_id值為20,在trx_ids列表內(nèi),所以不符合可見性要求,根據(jù)roll_pointer跳到下一個版本。
步驟3:下一個版本的列name的內(nèi)容是’錢七’,該版本的trx_id值為20,也在trx_ids列表內(nèi)合要求,繼續(xù)跳到下一個版本。
步驟4:下一個版本的列name的內(nèi)容是’王五’,該版本的trx_id值為10,而trx_ids列表中是包含值為10的事務(wù)id的,所以該版本也不符合要求,同理下一個列name的內(nèi)容是’李四’的版本也不符合要求。繼續(xù)跳到下個版本。
步聚5∶下一個版本的列name的內(nèi)容是’張三’,該版本的trx_id值為80,小于Readview中的up_limit_id值10,所以這個版本是符合要求的,最后返回給用戶的版本就是這條列c為‘張三’的記錄。
兩次SELECT查詢得到的結(jié)果是重復(fù)的,記錄的列c值都是’張三’,這就是可重復(fù)讀的含義。如果我們之后再把事務(wù)id為20的記錄提交了,然后再到剛才使用REPEATABLE READ隔離級刷的事務(wù)中繼續(xù)查找這個id為1的記錄,得到的結(jié)果還是’張三’,具體執(zhí)行過程大家可以自己分析一下。

5.3 如何解決幻讀

假設(shè)現(xiàn)在表student中只有一條數(shù)據(jù),數(shù)據(jù)內(nèi)容中,主鍵id=1,隱藏的trx_id=10,它的undo log如下圖所示。

假設(shè)現(xiàn)在有事務(wù)A和事務(wù)B并發(fā)執(zhí)行,事務(wù)A的事務(wù)id為20,事務(wù)B的事務(wù)id為30。
步驟1:事務(wù)A開始第一次查詢數(shù)據(jù),查詢的SQL語句如下。

select * from student where id > 1;

在開始查詢之前,MySQL會為事務(wù)A產(chǎn)生一個ReadView,此時ReadView的內(nèi)容如下: trx_ids=[20, 30 ] ,up_limit_id=20 , low_limit_id=31 , creator_trx_id=20。

由于此時表student中只有一條數(shù)據(jù),且符合where id>=1條件,因此會查詢出來。然后根據(jù)ReadView機制,發(fā)現(xiàn)該行數(shù)據(jù)的trx_id=10,小于事務(wù)A的ReadView里up_limit_id,這表示這條數(shù)據(jù)是事務(wù)A開啟之前,其他事務(wù)就已經(jīng)提交了的數(shù)據(jù),因此事務(wù)A可以讀取到。

結(jié)論:事務(wù)A的第一次查詢,能讀取到一條數(shù)據(jù),id=1。

步驟2∶接著事務(wù)B(trx_id=30),往表student中新插入兩條數(shù)據(jù),并提交事務(wù)。

insert into student(id,name) values(2,'李四');
insert into student(id,name) values(3,'王五');

此時表student中就有三條數(shù)據(jù)了,對應(yīng)的undo如下圖所示:

步驟3∶接著事務(wù)A開啟第二次查詢,根據(jù)可重復(fù)讀隔離級別的規(guī)則,此時事務(wù)A并不會再重新生成ReadView。此時表student中的3條數(shù)據(jù)都滿足 where id>=1的條件,因此會先查出來。然后根據(jù)ReadView機制,判斷每條數(shù)據(jù)是不是都可以被事務(wù)A看到。
1)首先 id=1的這條數(shù)據(jù),前面已經(jīng)說過了,可以被事務(wù)A看到。
2)然后是id=2的數(shù)據(jù),它的trx_id=30,此時事務(wù)A發(fā)現(xiàn),這個值處于up_limit_id和low_limit_id之間,因此還需要再判斷30是否處于trx_ids數(shù)組內(nèi)。由于事務(wù)A的trx_ids=[20,30],因此在數(shù)組內(nèi),這表示id=2的這條數(shù)據(jù)是與事務(wù)A在同一時刻啟動的其他事務(wù)提交的,所以這條數(shù)據(jù)不能讓事務(wù)A看到。
3)同理,id=3的這條數(shù)據(jù), trx_id 也為30,因此也不能被事務(wù)A看見。

結(jié)論:最終事務(wù)A的第二次查詢,只能查詢出id=1的這條數(shù)據(jù)。這和事務(wù)A的第一次查詢的結(jié)果是一樣的,因此沒有出現(xiàn)幻讀現(xiàn)象,所以說在 MySQL的可重復(fù)續(xù)隔離級別下,不存在幻讀問題。

6.總結(jié)

這里介紹了MVCCREAD COMNNITTD、REPEATABLE READ這兩種隔離級別的事務(wù)在執(zhí)行快照讀操作時訪問記錄的版本鏈的過程。這樣使不同事務(wù)的讀-寫、寫-讀操作并發(fā)執(zhí)行,從而提升系統(tǒng)性能。

核心點在于ReadView的原理,READ CONMITTD、REPEATABLE READ這兩個隔離級別的一個很大不同就是生成ReadView的時機不同:

READ COMMITTD在每一次進行普通SELECT操作前都會生成一個ReadViewREPEATABLE READ只在第一次進行普通SELECT操作前生成一個ReadView,之后的查詢操作都重復(fù)使用這個Readview就好了。

通過MVCC我們可以解決:

  • 1.讀寫之間阻塞的問題。通過MVcc可以讓讀寫互相不阻塞,即讀不阻塞寫,寫不阻塞讀,這樣就可以提升事務(wù)并發(fā)處理能力。
  • 2.降低了死鎖的概率。這是因為MVCC采用了樂觀鎖的方式,讀取數(shù)據(jù)時并不需要加鎖,對于寫操作,也只鎖定必要的行。
  • 3.解次快照讀的問題。當(dāng)我們查詢數(shù)據(jù)庫在某個時間點的快照時,只能看到這個時間點之前事務(wù)提交更新的結(jié)果,而不能看到這個時間點之后事務(wù)提交的更新結(jié)果。

到此這篇關(guān)于MySQL多版本并發(fā)控制MVCC詳解的文章就介紹到這了,更多相關(guān)MySQL MVCC內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論