MVCC深入理解(轉(zhuǎn)載)

1. MVCC簡(jiǎn)介

1.1 什么是MVCC

MVCC是一種多版本并發(fā)控制機(jī)制。

1.2 MVCC是為了解決什么問(wèn)題?

  • 大多數(shù)的MYSQL事務(wù)型存儲(chǔ)引擎,如,InnoDB,F(xiàn)alcon以及PBXT都不使用一種簡(jiǎn)單的行鎖機(jī)制.事實(shí)上,他們都和MVCC–多版本并發(fā)控制來(lái)一起使用.
  • 大家都應(yīng)該知道,鎖機(jī)制可以控制并發(fā)操作,但是其系統(tǒng)開(kāi)銷較大,而MVCC可以在大多數(shù)情況下代替行級(jí)鎖,使用MVCC,能降低其系統(tǒng)開(kāi)銷.

1.3 MVCC實(shí)現(xiàn)

MVCC是通過(guò)保存數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)的快照來(lái)實(shí)現(xiàn)的. 不同存儲(chǔ)引擎的MVCC. 不同存儲(chǔ)引擎的MVCC實(shí)現(xiàn)是不同的,典型的有樂(lè)觀并發(fā)控制和悲觀并發(fā)控制.

2.MVCC 具體實(shí)現(xiàn)分析

下面,我們通過(guò)InnoDB的MVCC實(shí)現(xiàn)來(lái)分析MVCC使怎樣進(jìn)行并發(fā)控制的.
InnoDB的MVCC,是通過(guò)在每行記錄后面保存兩個(gè)隱藏的列來(lái)實(shí)現(xiàn)的,這兩個(gè)列,分別保存了這個(gè)行的創(chuàng)建時(shí)間,一個(gè)保存的是行的刪除時(shí)間。這里存儲(chǔ)的并不是實(shí)際的時(shí)間值,而是系統(tǒng)版本號(hào)(可以理解為事務(wù)的ID),沒(méi)開(kāi)始一個(gè)新的事務(wù),系統(tǒng)版本號(hào)就會(huì)自動(dòng)遞增,事務(wù)開(kāi)始時(shí)刻的系統(tǒng)版本號(hào)會(huì)作為事務(wù)的ID.下面看一下在REPEATABLE READ隔離級(jí)別下,MVCC具體是如何操作的.

2.1簡(jiǎn)單的小例子

create table yang(
id int primary key auto_increment,
name varchar(20));

假設(shè)系統(tǒng)的版本號(hào)從1開(kāi)始.

INSERT

InnoDB為新插入的每一行保存當(dāng)前系統(tǒng)版本號(hào)作為版本號(hào).
第一個(gè)事務(wù)ID為1;

start transaction;
insert into yang values(NULL,'yang') ;
insert into yang values(NULL,'long');
insert into yang values(NULL,'fei');
commit;
  • 1
  • 2
  • 3
  • 4
  • 5

對(duì)應(yīng)在數(shù)據(jù)中的表如下(后面兩列是隱藏列,我們通過(guò)查詢語(yǔ)句并看不到)

id name 創(chuàng)建時(shí)間(事務(wù)ID) 刪除時(shí)間(事務(wù)ID)
1 yang 1 undefined
2 long 1 undefined
3 fei 1 undefined

SELECT

InnoDB會(huì)根據(jù)以下兩個(gè)條件檢查每行記錄:
a.InnoDB只會(huì)查找版本早于當(dāng)前事務(wù)版本的數(shù)據(jù)行(也就是,行的系統(tǒng)版本號(hào)小于或等于事務(wù)的系統(tǒng)版本號(hào)),這樣可以確保事務(wù)讀取的行,要么是在事務(wù)開(kāi)始前已經(jīng)存在的,要么是事務(wù)自身插入或者修改過(guò)的.
b.行的刪除版本要么未定義,要么大于當(dāng)前事務(wù)版本號(hào),這可以確保事務(wù)讀取到的行,在事務(wù)開(kāi)始之前未被刪除.
只有a,b同時(shí)滿足的記錄,才能返回作為查詢結(jié)果.

DELETE

InnoDB會(huì)為刪除的每一行保存當(dāng)前系統(tǒng)的版本號(hào)(事務(wù)的ID)作為刪除標(biāo)識(shí).
看下面的具體例子分析:
第二個(gè)事務(wù),ID為2;

start transaction;
select * from yang;  //(1)
select * from yang;  //(2)
commit; 
  • 1
  • 2
  • 3
  • 4

假設(shè)1

假設(shè)在執(zhí)行這個(gè)事務(wù)ID為2的過(guò)程中,剛執(zhí)行到(1),這時(shí),有另一個(gè)事務(wù)ID為3往這個(gè)表里插入了一條數(shù)據(jù);
第三個(gè)事務(wù)ID為3;

start transaction;
insert into yang values(NULL,'tian');
commit;
  • 1
  • 2
  • 3

這時(shí)表中的數(shù)據(jù)如下:

id name 創(chuàng)建時(shí)間(事務(wù)ID) 刪除時(shí)間(事務(wù)ID)
1 yang 1 undefined
2 long 1 undefined
3 fei 1 undefined
4 tian 3 undefined

然后接著執(zhí)行事務(wù)2中的(2),由于id=4的數(shù)據(jù)的創(chuàng)建時(shí)間(事務(wù)ID為3),執(zhí)行當(dāng)前事務(wù)的ID為2,而InnoDB只會(huì)查找事務(wù)ID小于等于當(dāng)前事務(wù)ID的數(shù)據(jù)行,所以id=4的數(shù)據(jù)行并不會(huì)在執(zhí)行事務(wù)2中的(2)被檢索出來(lái),在事務(wù)2中的兩條select 語(yǔ)句檢索出來(lái)的數(shù)據(jù)都只會(huì)下表:

id name 創(chuàng)建時(shí)間(事務(wù)ID) 刪除時(shí)間(事務(wù)ID)
1 yang 1 undefined
2 long 1 undefined
3 fei 1 undefined

假設(shè)2

假設(shè)在執(zhí)行這個(gè)事務(wù)ID為2的過(guò)程中,剛執(zhí)行到(1),假設(shè)事務(wù)執(zhí)行完事務(wù)3后,接著又執(zhí)行了事務(wù)4;
第四個(gè)事務(wù):

start   transaction;  
delete from yang where id=1;
commit;  
  • 1
  • 2
  • 3

此時(shí)數(shù)據(jù)庫(kù)中的表如下:

id name 創(chuàng)建時(shí)間(事務(wù)ID) 刪除時(shí)間(事務(wù)ID)
1 yang 1 4
2 long 1 undefined
3 fei 1 undefined
4 tian 3 undefined

接著執(zhí)行事務(wù)ID為2的事務(wù)(2),根據(jù)SELECT 檢索條件可以知道,它會(huì)檢索創(chuàng)建時(shí)間(創(chuàng)建事務(wù)的ID)小于當(dāng)前事務(wù)ID的行和刪除時(shí)間(刪除事務(wù)的ID)大于當(dāng)前事務(wù)的行,而id=4的行上面已經(jīng)說(shuō)過(guò),而id=1的行由于刪除時(shí)間(刪除事務(wù)的ID)大于當(dāng)前事務(wù)的ID,所以事務(wù)2的(2)select * from yang也會(huì)把id=1的數(shù)據(jù)檢索出來(lái).所以,事務(wù)2中的兩條select 語(yǔ)句檢索出來(lái)的數(shù)據(jù)都如下:

id name 創(chuàng)建時(shí)間(事務(wù)ID) 刪除時(shí)間(事務(wù)ID)
1 yang 1 4
2 long 1 undefined
3 fei 1 undefined

UPDATE

InnoDB執(zhí)行UPDATE,實(shí)際上是新插入了一行記錄,并保存其創(chuàng)建時(shí)間為當(dāng)前事務(wù)的ID,同時(shí)保存當(dāng)前事務(wù)ID到要UPDATE的行的刪除時(shí)間.

假設(shè)3

假設(shè)在執(zhí)行完事務(wù)2的(1)后又執(zhí)行,其它用戶執(zhí)行了事務(wù)3,4,這時(shí),又有一個(gè)用戶對(duì)這張表執(zhí)行了UPDATE操作:
第5個(gè)事務(wù):

start  transaction;
update yang set name='Long' where id=2;
commit;
  • 1
  • 2
  • 3

根據(jù)update的更新原則:會(huì)生成新的一行,并在原來(lái)要修改的列的刪除時(shí)間列上添加本事務(wù)ID,得到表如下:

id name 創(chuàng)建時(shí)間(事務(wù)ID) 刪除時(shí)間(事務(wù)ID)
1 yang 1 4
2 long 1 5
3 fei 1 undefined
4 tian 3 undefined
2 Long 5 undefined

繼續(xù)執(zhí)行事務(wù)2的(2),根據(jù)select 語(yǔ)句的檢索條件,得到下表:

id name 創(chuàng)建時(shí)間(事務(wù)ID) 刪除時(shí)間(事務(wù)ID)
1 yang 1 4
2 long 1 5
3 fei 1 undefined

還是和事務(wù)2中(1)select 得到相同的結(jié)果.

2:

MySQL InnoDB存儲(chǔ)引擎,實(shí)現(xiàn)的是基于多版本的并發(fā)控制協(xié)議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對(duì)的,是基于鎖的并發(fā)控制,Lock-Based Concurrency Control)。MVCC最大的好處,相信也是耳熟能詳:讀不加鎖,讀寫(xiě)不沖突。在讀多寫(xiě)少的OLTP應(yīng)用中,讀寫(xiě)不沖突是非常重要的,極大的增加了系統(tǒng)的并發(fā)性能。

InnoDB在每行數(shù)據(jù)都增加兩個(gè)隱藏字段,一個(gè)記錄創(chuàng)建的版本號(hào),一個(gè)記錄刪除的版本號(hào)。

  • SELECT:
    當(dāng)隔離級(jí)別是REPEATABLE READ時(shí)select操作,InnoDB必須每行數(shù)據(jù)來(lái)保證它符合兩個(gè)條件:
    1、InnoDB必須找到一個(gè)行的版本,它至少要和事務(wù)的版本一樣老(也即它的版本號(hào)不大于事務(wù)的版本號(hào))。這保證了不管是事務(wù)開(kāi)始之前,或者事務(wù)創(chuàng)建時(shí),或者修改了這行數(shù)據(jù)的時(shí)候,這行數(shù)據(jù)是存在的。
    2、這行數(shù)據(jù)的刪除版本必須是未定義的或者比事務(wù)版本要大。這可以保證在事務(wù)開(kāi)始之前這行數(shù)據(jù)沒(méi)有被刪除。
    符合這兩個(gè)條件的行可能會(huì)被當(dāng)作查詢結(jié)果而返回。
  • INSERT:
    InnoDB為這個(gè)新行記錄當(dāng)前的系統(tǒng)版本號(hào)。
  • DELETE:
    InnoDB將當(dāng)前的系統(tǒng)版本號(hào)設(shè)置為這一行的刪除ID。
  • UPDATE:
    InnoDB會(huì)寫(xiě)一個(gè)這行數(shù)據(jù)的新拷貝,這個(gè)拷貝的版本為當(dāng)前的系統(tǒng)版本號(hào)。它同時(shí)也會(huì)將這個(gè)版本號(hào)寫(xiě)到舊行的刪除版本里。
    這種額外的記錄所帶來(lái)的結(jié)果就是對(duì)于大多數(shù)查詢來(lái)說(shuō)根本就不需要獲得一個(gè)鎖。他們只是簡(jiǎn)單地以最快的速度來(lái)讀取數(shù)據(jù),確保只選擇符合條件的行。這個(gè)方案的缺點(diǎn)在于存儲(chǔ)引擎必須為每一行存儲(chǔ)更多的數(shù)據(jù),
    做更多的檢查工作,處理更多的善后操作。
    MVCC只工作在REPEATABLE READ和READ COMMITED隔離級(jí)別下。READ UNCOMMITED不是MVCC兼容的,因?yàn)椴樵儾荒苷业竭m合他們事務(wù)版本的行版本;它們每次都只能讀到最新的版本。
    SERIABLABLE也不與MVCC兼容,因?yàn)樽x操作會(huì)鎖定他們返回的每一行數(shù)據(jù)。

3:

一、基礎(chǔ)知識(shí)

事務(wù): 事務(wù)是一組原子性sql查詢語(yǔ)句,被當(dāng)作一個(gè)工作單元。若mysql對(duì)改事務(wù)單元內(nèi)的所有sql語(yǔ)句都正常的執(zhí)行完,則事務(wù)操作視為成功,所有的sql語(yǔ)句才對(duì)數(shù)據(jù)生效,若sql中任意不能執(zhí)行或出錯(cuò)則事務(wù)操作失敗,所有對(duì)數(shù)據(jù)的操作則無(wú)效(通過(guò)回滾恢復(fù)數(shù)據(jù))。事務(wù)有四個(gè)屬性:

1、原子性:事務(wù)被認(rèn)為不可分的一個(gè)工作單元,要么全部正常執(zhí)行,要么全部不執(zhí)行。

2、一致性:事務(wù)操作對(duì)數(shù)據(jù)庫(kù)總是從一種一致性的狀態(tài)轉(zhuǎn)換成另外一種一致性狀態(tài)。

3、隔離性:一個(gè)事務(wù)的操作結(jié)果在內(nèi)部一致,可見(jiàn),而對(duì)除自己以外的事務(wù)是不可見(jiàn)的。

4、永久性:事務(wù)在未提交前數(shù)據(jù)一般情況下可以回滾恢復(fù)數(shù)據(jù),一旦提交(commit)數(shù)據(jù)的改變則變成永久(當(dāng)然用update肯定還能修改)。

ps:MYSAM 引擎的數(shù)據(jù)庫(kù)不支持事務(wù),所以事務(wù)最好不要對(duì)混合引擎(如INNODB 、MYISAM)操作,若能正常運(yùn)行且是你想要的最好,否則事務(wù)中對(duì)非支持事務(wù)表的操作是不能回滾恢復(fù)的。

讀鎖:也叫共享鎖、S鎖,若事務(wù)T對(duì)數(shù)據(jù)對(duì)象A加上S鎖,則事務(wù)T可以讀A但不能修改A,其他事務(wù)只能再對(duì)A加S鎖,而不能加X(jué)鎖,直到T釋放A上的S 鎖。這保證了其他事務(wù)可以讀A,但在T釋放A上的S鎖之前不能對(duì)A做任何修改。

寫(xiě)鎖:又稱排他鎖、X鎖。若事務(wù)T對(duì)數(shù)據(jù)對(duì)象A加上X鎖,事務(wù)T可以讀A也可以修改A,其他事務(wù)不能再對(duì)A加任何鎖,直到T釋放A上的鎖。這保證了其他事務(wù)在T釋放A上的鎖之前不能再讀取和修改A。

表鎖: 操作對(duì)象是數(shù)據(jù)表。Mysql大多數(shù)鎖策略都支持(常見(jiàn)mysql innodb),是系統(tǒng)開(kāi)銷最低但并發(fā)性最低的一個(gè)鎖策略。事務(wù)t對(duì)整個(gè)表加讀鎖,則其他事務(wù)可讀不可寫(xiě),若加寫(xiě)鎖,則其他事務(wù)增刪改都不行。

行級(jí)鎖:操作對(duì)象是數(shù)據(jù)表中的一行。是MVCC技術(shù)用的比較多的,但在MYISAM用不了,行級(jí)鎖用mysql的儲(chǔ)存引擎實(shí)現(xiàn)而不是mysql服務(wù)器。但行級(jí)鎖對(duì)系統(tǒng)開(kāi)銷較大,處理高并發(fā)較好。

MVCC: 多版本并發(fā)控制(MVCC,Multiversion Currency Control)。一般情況下,事務(wù)性儲(chǔ)存引擎不是只使用表鎖,行加鎖的處理數(shù)據(jù),而是結(jié)合了MVCC機(jī)制,以處理更多的并發(fā)問(wèn)題。Mvcc處理高并發(fā)能力最強(qiáng),但系統(tǒng)開(kāi)銷比最大(較表鎖、行級(jí)鎖),這是最求高并發(fā)付出的代價(jià)。

Autocommit: mysql一個(gè)系統(tǒng)變量,默認(rèn)情況下autocommit=1表示mysql把沒(méi)一條sql語(yǔ)句自動(dòng)的提交,而不用commit語(yǔ)句。所以,當(dāng)要開(kāi)啟事務(wù)操作時(shí),要把a(bǔ)utocommit設(shè)為0,可以通過(guò)“set session autocommit=0; ”來(lái)設(shè)置

二、MVCC實(shí)現(xiàn)原理以及例化理解

第一:先看看網(wǎng)絡(luò)上幾乎全部一樣的理解,包括《高性能mysql第二版(中文版)》也如此說(shuō)明,這樣是很容易理解。但筆者覺(jué)得2個(gè)地方不妥,先看內(nèi)容,在后面筆者會(huì)給出不妥地方用(1、2…)加粗標(biāo)志出來(lái),且給出測(cè)試證明。

Ps:這些只是外部看來(lái)的理解層面,深層次在第三點(diǎn)講解


InnoDB實(shí)現(xiàn)MVCC的方法是,它存儲(chǔ)了每一行的兩個(gè)(1)額外的隱藏字段,這兩個(gè)隱藏字段分別記錄了行的創(chuàng)建的時(shí)間和刪除的時(shí)間。在每個(gè)事件發(fā)生的時(shí)候,每行存儲(chǔ)版本號(hào),而不是存儲(chǔ)事件實(shí)際發(fā)生的時(shí)間。每次事物的開(kāi)始這個(gè)版本號(hào)都會(huì)增加。自記錄時(shí)間開(kāi)始,每個(gè)事物都會(huì)保存記錄的系統(tǒng)版本號(hào)。依照事物的 版本來(lái)檢查每行的版本號(hào)。在事物隔離級(jí)別為可重復(fù)讀的情況下,來(lái)看看怎樣應(yīng)用它。

SELECT

Innodb檢查沒(méi)行數(shù)據(jù),確保他們符合兩個(gè)標(biāo)準(zhǔn):

1、InnoDB只查找版本早于當(dāng)前事務(wù)版本的數(shù)據(jù)行(也就是數(shù)據(jù)行的版本必須小于等于事務(wù)的版本),這確保當(dāng)前事務(wù)讀取的行都是事務(wù)之前已經(jīng)存在的,或者是由當(dāng)前事務(wù)創(chuàng)建或修改的行

2、行的刪除操作的版本一定是未定義的或者大于當(dāng)前事務(wù)的版本號(hào)。確定了當(dāng)前事務(wù)開(kāi)始之前,行沒(méi)有被刪除(2)

符合了以上兩點(diǎn)則返回查詢結(jié)果。

INSERT **(3) **

InnoDB為每個(gè)新增行記錄當(dāng)前系統(tǒng)版本號(hào)作為創(chuàng)建ID。

DELETE

InnoDB為每個(gè)刪除行的記錄當(dāng)前系統(tǒng)版本號(hào)作為行的刪除ID。

UPDATE

InnoDB復(fù)制了一行。這個(gè)新行的版本號(hào)使用了系統(tǒng)版本號(hào)。它也把系統(tǒng)版本號(hào)作為了刪除行的版本。


(1) 不是兩個(gè),是三個(gè)。

1DB_TRX_ID:一個(gè)6byte的標(biāo)識(shí),每處理一個(gè)事務(wù),其值自動(dòng)+1,上述說(shuō)到的“創(chuàng)建時(shí)間”和“刪除時(shí)間”記錄的就是這個(gè)DB_TRX_ID的值,如insert、update、delete操作時(shí),刪除操作用1個(gè)bit表示。 DB_TRX_ID是最重要的一個(gè),可以通過(guò)語(yǔ)句“show engine innodb status”來(lái)查找,如下:


      TRANSACTIONS

Trx id counter 0 430621

Purge done for trx's n:o < 0 430136 undo n:o < 0 0

History list length 7


2DB_ROLL_PTR: 大小是7byte,指向?qū)懙絩ollback segment(回滾段)的一條undo log記錄(update操作的話,記錄update前的ROW值)

3DB_ROW_ID: 大小是6byte,該值隨新行插入單調(diào)增加,當(dāng)由innodb自動(dòng)產(chǎn)生聚集索引時(shí),聚集索引包括這個(gè)DB_ROW_ID的值,不然的話聚集索引中不包括這個(gè)值. 這個(gè)用于索引當(dāng)中

(2) 這里的不是真正的刪除數(shù)據(jù),而是標(biāo)志出來(lái)的刪除。真正意義的刪除是在commit的時(shí)候。網(wǎng)上的說(shuō)法很容易讓讀者誤解

**(3) **在insert操作時(shí) “創(chuàng)建時(shí)間”=DB_ROW_ID,這時(shí),“刪除時(shí)間 ”是未定義的;在update時(shí),復(fù)制新增行的“創(chuàng)建時(shí)間”=DB_ROW_ID,刪除時(shí)間未定義,舊數(shù)據(jù)行“創(chuàng)建時(shí)間”不變,刪除時(shí)間=該事務(wù)的DB_ROW_ID;delete操作,相應(yīng)數(shù)據(jù)行的“創(chuàng)建時(shí)間”不變,刪除時(shí)間=該事務(wù)的DB_ROW_ID;select操作對(duì)兩者都不修改,只讀相應(yīng)的數(shù)據(jù)

第二、下面用圖形化形式表示MVCC如何處理select、insert、delete、update

有兩個(gè)事務(wù)A、B

假設(shè)開(kāi)始時(shí)間順序ABCD,且DB_TRX_ID滿足以下情況

A. DB_TRX_ID = 2010

B. DB_TRX_ID = 2011

C. DB_TRX_ID = 2012

D. DB_TRX_ID = 2013

注意:

1、B. DB_TRX_ID> A. DB_TRX_ID是因?yàn)镈B_TRX_ID的值是系統(tǒng)版本號(hào)的值,系統(tǒng)版本號(hào)是自動(dòng)增加的,所以DB_TRX_ID也是自動(dòng)增加。但是會(huì)出現(xiàn)這種情況,假如A事務(wù)開(kāi)始后B事務(wù)開(kāi)始前有一個(gè)insert操作插入一行數(shù)據(jù)(沒(méi)有bengin、comint),則B. DB_TRX_ID= A. DB_TRX_ID+1+1 ,并不符合不是說(shuō)系統(tǒng)版本號(hào)增量為1,其實(shí)并不矛盾,其實(shí)每一條sql操作可以當(dāng)作一個(gè)事務(wù),因?yàn)閍utocommit=1,所以這個(gè)insert操作是一個(gè)事務(wù),A事務(wù)之后新增2個(gè)事務(wù), 所以是加2而不是1。

2、下面例化圖只是筆者方便大家理解而設(shè)計(jì)的圖片,紅色框代表隱藏兩列

例化1:SECLET

這是表test數(shù)據(jù)

trx代表改行數(shù)據(jù)是那個(gè)事務(wù)創(chuàng)建

creat_num是“創(chuàng)建時(shí)間”,也就是DB_TRX_ID值

dele_num是“刪除時(shí)間 ”,空列代表沒(méi)被任何事務(wù)標(biāo)志為已“刪除”,圖中id為2的數(shù)據(jù)行的dele_num=2012表示事務(wù)C“刪除”了改行。

[圖片上傳失敗...(image-8af159-1581431533642)]

B事務(wù)有select * from test;語(yǔ)句,按照MVCC原理,該語(yǔ)句相當(dāng)于:select * from test where creat_num>=2011 and (dele_num=NULL OR dele_num>2011),所以返回?cái)?shù)據(jù)是id為1、2行。

D事務(wù)select * from test;則返回出id為2的行。因?yàn)?行被C事務(wù)刪除了。

例化2:UPDATE

[圖片上傳失敗...(image-eaf149-1581431533642)]

A事務(wù)一條語(yǔ)句“update from test set col=’winben’ where col=’benwin’”。

則先復(fù)制一條數(shù)據(jù)如藍(lán)色框,creat_num=DB_TRX_ID(這里是2010),dele_num=NULL,然后把舊行數(shù)據(jù)的設(shè)dele_num=2010,等commit后則刪除舊數(shù)據(jù)行

例化3:DELET

刪除就是設(shè)dele_num= DB_TRX_ID

-------于2012.12.23加上start----------------------------------------------------------------

和一位淘寶網(wǎng)友討論一個(gè)問(wèn)題(關(guān)于事務(wù)隔離級(jí)別,這里就直接貼不整理了)

網(wǎng)友: 請(qǐng)教個(gè)問(wèn)題,innodb的事務(wù),一定是按ID順序提交么? ID為101的一定在ID為100的事務(wù)之后?

筆者:這個(gè)問(wèn)題我也不確定。我認(rèn)為不是按順序的,可以這樣想一下,加入a事務(wù)很大是id100,然后還沒(méi)commit之前有id為101的事務(wù)b并發(fā)開(kāi)始處理,但b事務(wù)很小處理完了,如果要等a事務(wù)的話則是一個(gè)雞肋了。當(dāng)然還有考慮鎖的問(wèn)題,如果a事務(wù)設(shè)置了排他鎖,且b事務(wù)有寫(xiě)操作那不事務(wù)則在等待隊(duì)列中了,那commit的順序肯定是a然后b的!

網(wǎng)友:如果是這樣的話,假設(shè)有100,101,102三個(gè)事務(wù),101最先提交了,這時(shí)新事務(wù)103,應(yīng)該能看到101的更改,而如果按當(dāng)前活躍ID的最小的比較(這時(shí)為100),那就看不到101的更新。

筆者:結(jié)合事務(wù)隔離級(jí)別:

1、READ UNCOMMITTED ,不適用MVCC讀,可以讀到其他事務(wù)修改甚至未提交的

2、READ COMMITTED ,其他事務(wù)對(duì)數(shù)據(jù)庫(kù)的修改,只要已經(jīng)提交,其修改的結(jié)果就是可見(jiàn)的,與這兩個(gè)事務(wù)開(kāi)始的先后順序無(wú)關(guān),不完全適用于MVCC讀,

像你說(shuō)的100的讀101的是可以的(按照MVCC理論應(yīng)該不行的),但適用102讀101(能套MVCC理論)。

3、REPEATABLE READ,可重復(fù)讀,完全適用MVCC,只能讀取在它開(kāi)始之前已經(jīng)提交的事務(wù)對(duì)數(shù)據(jù)庫(kù)的修改,在它開(kāi)始以后,所有其他事務(wù)對(duì)數(shù)據(jù)庫(kù)的修改對(duì)它來(lái)說(shuō)均不可見(jiàn)

4、 SERIALIZABLE ,完全不適合適用MVCC,這樣所有的query都會(huì)加鎖,再它之后的事務(wù)都要等待

MVCC只工作在REPEATABLE READ和READ COMMITED隔離級(jí)別下

三、深入MVCC實(shí)現(xiàn)機(jī)制

1、到這里很多人就會(huì)發(fā)現(xiàn),如果確實(shí)根據(jù)creat_num 即時(shí)事務(wù)DB_TRX_ID去比較獲取事務(wù)的話,按道理在一個(gè)事務(wù)B(比A后,但A還沒(méi)commit)select的話B. DB_TRX_ID>A.DB_TRX_ID則應(yīng)該能返回A事務(wù)對(duì)數(shù)據(jù)的操作以及修改。那不是和前面矛盾?其實(shí)不然,只是前面沒(méi)有講到以下內(nèi)容。

InnoDB每個(gè)事務(wù)在開(kāi)始的時(shí)候,會(huì)將當(dāng)前系統(tǒng)中的活躍事務(wù)列表(trx_sys->trx_list)創(chuàng)建一個(gè)副本(read view),然后一致性讀去比較記錄的tx id的時(shí)候,并不是根據(jù)當(dāng)前事務(wù)的tx id,而是根據(jù)read view最早一個(gè)事務(wù)的tx id(read view->up_limit_id)來(lái)做比較的,這樣就能確保在事務(wù)B之前沒(méi)有提交的所有事務(wù)的變更,B事務(wù)都是看不到的。當(dāng)然,這里還有個(gè)小問(wèn)題要處理一下,就是當(dāng)前事務(wù)自身的變更還是需要看到的。

[圖片上傳失敗...(image-abfa45-1581431533642)]

在storage/innobase/read/read0read.c中實(shí)現(xiàn)了創(chuàng)建read view的函數(shù)read_view_open_now,在storage/innobase/include/read0read.ic中實(shí)現(xiàn)了判斷一致性讀是否可見(jiàn)的read_view_sees_trx_id

代碼:

1.  read_view_t*  
2.  read_view_open_now(  
3.  /*===============*/  
4.  trx_id_t    cr_trx_id,          /*!< in: trx_id of creating 
5.  transaction, or 0 used in purge */  
6.  mem_heap_t*          heap)                 /*!< in: memory heap from which 
7.  allocated */  
8.  {  
9.  read_view_t*  view;  
10.  trx_t*                trx;  
11.  ulint          n;  
12.  ut_ad(mutex_own(&kernel_mutex));  
13.  view = read_view_create_low(UT_LIST_GET_LEN(trx_sys->trx_list), heap);  
14.  view->creator_trx_id = cr_trx_id;  
15.  view->type = VIEW_NORMAL;  
16.  view->undo_no = 0;  
17.  /* No future transactions should be visible in the view */  
18.  view->low_limit_no = trx_sys->max_trx_id;  
19.  view->low_limit_id = view->low_limit_no;  
20.  n = 0;  
21.  trx = UT_LIST_GET_FIRST(trx_sys->trx_list);  
22.  /* No active transaction should be visible, except cr_trx */  
23.  while (trx) {  
24.  if (trx->id != cr_trx_id  
25.  && (trx->conc_state == TRX_ACTIVE  
26.  || trx->conc_state == TRX_PREPARED)) {  
27.  read_view_set_nth_trx_id(view, n, trx->id);  
28.  n++;  
29.  /* NOTE that a transaction whose trx number is < 
30.  trx_sys->max_trx_id can still be active, if it is 
31.  in the middle of its commit! Note that when a 
32.  transaction starts, we initialize trx->no to 
33.  IB_ULONGLONG_MAX. */  
34.  if (view->low_limit_no > trx->no) {  
35.  view->low_limit_no = trx->no;  
36.  }  
37.  }  
38.  trx = UT_LIST_GET_NEXT(trx_list, trx);  
39.  }  
40.  view->n_trx_ids = n;  
41.  if (n > 0) {  
42.  /* The last active transaction has the smallest id: */  
43.  view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);  
44.  } else {  
45.  view->up_limit_id = view->low_limit_id;  
46.  }  
47.  UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);  
48.  return(view);  

50.  }  

2、MVCC如何控制update操作

前面說(shuō)先復(fù)制新數(shù)據(jù),并插入DB_TRX_ID的值,在把舊數(shù)據(jù)的刪除標(biāo)志DB_TRX_ID

現(xiàn)在先介紹幾個(gè)概念:

DB_ROLL_PTR是指向回滾段中舊版本7byte回滾指針。

redo log:重做日志,就是每次mysql在執(zhí)行寫(xiě)入數(shù)據(jù)前先把要寫(xiě)的信息保存在重寫(xiě)日志中,但出現(xiàn)斷電,奔潰,重啟等等導(dǎo)致數(shù)據(jù)不能正常寫(xiě)入期望數(shù)據(jù)時(shí),服務(wù)器可以通過(guò)redo_log中的信息重新寫(xiě)入數(shù)據(jù)。

undo log:撤銷日志,與redo log恰恰相反,當(dāng)一些更改在執(zhí)行一半時(shí),發(fā)生意外,而無(wú)法完成,則可以根據(jù)撤消日志恢復(fù)到更改之前的壯態(tài)。

mvcc中update步驟:

1、 記錄事務(wù)中修改行數(shù)據(jù)的相應(yīng)字段和值(包括舊版本事務(wù)id)在undo-log中記錄。

2、 修改相應(yīng)數(shù)據(jù)。

3、 在redo-log中保存要修改的相應(yīng)(新版本事務(wù)id)數(shù)據(jù)寫(xiě)入

以上驟詳細(xì)代碼內(nèi)容可看:

http://hi.baidu.com/gao1738/blog/item/dcec39d6185af2049d163d8c.html

4、 假如update不能正常運(yùn)行怎根據(jù)undo-log redo-log 來(lái)回復(fù)

5、 當(dāng)然如果當(dāng)前版本事務(wù)沒(méi)有commit的話則通過(guò)undo-log信息恢復(fù)原始數(shù)據(jù)狀態(tài).

轉(zhuǎn)載自https://www.cnblogs.com/liulvzhong/articles/9242299.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容