MySQL的MVCC機制


1、MVCC簡介

1.1 MVCC是什么?

MVCC,Multi-Version Concurrency Control,多版本並發控制。MVCC 是一種並發控制的方法,一般在數據庫管理系統中,實現對數據庫的並發訪問;

1.2 MVCC是為了解決什么?

  • 大多數的MYSQL事務型存儲引擎,如,InnoDB,Falcon以及PBXT都不使用一種簡單的行鎖機制.事實上,他們都和MVCC–多版本並發控制來一起使用
  • 大家都應該知道,鎖機制可以控制並發操作,但是其系統開銷較大,而MVCC可以在大多數情況下代替行級鎖,使用MVCC,能降低其系統開銷

  眾所周知,在MYSQL中,MyISAM使用的是表鎖,InnoDB使用的是行鎖。而InnoDB的事務分為四個隔離級別,其中默認的隔離級別REPEATABLE READ需要兩個不同的事務相互之間不能影響,而且還能支持並發,這點悲觀鎖是達不到的,所以REPEATABLE READ采用的就是樂觀鎖,而樂觀鎖的實現采用的就是MVCC。正是因為有了MVCC,才造就了InnoDB強大的事務處理能力。

 

MVCC解決的問題是讀寫互相不阻塞的問題,每次更新都產生一個新的版本,讀的話可以讀歷史版本。試想,如果一個數據只有一個版本,那么多個事務對這個數據進行讀寫是不是需要讀寫鎖來保護?
一個讀寫事務在運行的過程中在訪問數據之前先加讀/寫鎖這種實現叫做悲觀鎖,悲觀體現在,先加鎖,獨占數據,防止別人加鎖。
樂觀鎖呢,讀寫事務,在真正的提交之前,不加讀/寫鎖,而是先看一下數據的版本/時間戳,等到真正提交的時候再看一下版本/時間戳,如果兩次相同,說明別人期間沒有對數據進行過修改,那么就可以放心提交。
樂觀體現在,訪問數據時不提前加鎖。在資源沖突不激烈的場合,用樂觀鎖性能較好。如果資源沖突嚴重,樂觀鎖的實現會導致事務提交的時候經常看到別人在他之前已經修改了數據,然后要進行回滾或者重試,還不如一上來就加鎖。

所以通常我們把沒有開啟MVCC特性的,使用原來的鎖機制來保證數據一致性的這種鎖叫悲觀鎖,
而對開啟MVCC機制的鎖,叫做樂觀鎖。

1.3 MVCC實現

  MVCC是通過保存數據在某個時間點的快照來實現的. 不同存儲引擎的MVCC實現是不同的,典型的有樂觀並發控制和悲觀並發控制.

2、MVCC具體實現

下面,我們通過InnoDB的MVCC實現來分析MVCC使怎樣進行並發控制的.
InnoDB的MVCC,是通過在每行記錄后面保存兩個隱藏的列來實現的,這兩個列,分別保存了這個行的創建時間,一個保存的是行的刪除時間。這里存儲的並不是實際的時間值,而是系統版本號(可以理解為事務的ID),沒開始一個新的事務,系統版本號就會自動遞增,事務開始時刻的系統版本號會作為事務的ID.下面看一下在REPEATABLE READ隔離級別下,MVCC具體是如何操作的.

2.1簡單的小例子

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

假設系統的版本號從1開始.

INSERT

InnoDB為新插入的每一行保存當前系統版本號作為版本號.
第一個事務ID為1;

start transaction; insert into yang values(NULL,'yang') ; insert into yang values(NULL,'long'); insert into yang values(NULL,'fei'); commit;

對應在數據中的表如下(后面兩列是隱藏列,我們通過查詢語句並看不到)

id name 創建時間(事務ID) 刪除時間(事務ID)
1 yang 1 undefined
2 long 1 undefined
3 fei 1 undefined

SELECT

InnoDB會根據以下兩個條件檢查每行記錄:
a.InnoDB只會查找版本早於當前事務版本的數據行(也就是,行的系統版本號小於或等於事務的系統版本號),這樣可以確保事務讀取的行,要么是在事務開始前已經存在的,要么是事務自身插入或者修改過的.
b.行的刪除版本要么未定義,要么大於當前事務版本號,這可以確保事務讀取到的行,在事務開始之前未被刪除.
只有a,b同時滿足的記錄,才能返回作為查詢結果.

DELETE

InnoDB會為刪除的每一行保存當前系統的版本號(事務的ID)作為刪除標識.
看下面的具體例子分析:
第二個事務,ID為2;

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

假設1

假設在執行這個事務ID為2的過程中,剛執行到(1),這時,有另一個事務ID為3往這個表里插入了一條數據;
第三個事務ID為3;

start transaction; insert into yang values(NULL,'tian'); commit;

這時表中的數據如下:

id name 創建時間(事務ID) 刪除時間(事務ID)
1 yang 1 undefined
2 long 1 undefined
3 fei 1 undefined
4 tian 3 undefined

然后接着執行事務2中的(2),由於id=4的數據的創建時間(事務ID為3),執行當前事務的ID為2,而InnoDB只會查找事務ID小於等於當前事務ID的數據行,所以id=4的數據行並不會在執行事務2中的(2)被檢索出來,在事務2中的兩條select 語句檢索出來的數據都只會下表:

id name 創建時間(事務ID) 刪除時間(事務ID)
1 yang 1 undefined
2 long 1 undefined
3 fei 1 undefined

假設2

假設在執行這個事務ID為2的過程中,剛執行到(1),假設事務執行完事務3后,接着又執行了事務4;
第四個事務:

start transaction; delete from yang where id=1; commit; 

此時數據庫中的表如下:

id name 創建時間(事務ID) 刪除時間(事務ID)
1 yang 1 4
2 long 1 undefined
3 fei 1 undefined
4 tian 3 undefined

接着執行事務ID為2的事務(2),根據SELECT 檢索條件可以知道,它會檢索創建時間(創建事務的ID)小於當前事務ID的行和刪除時間(刪除事務的ID)大於當前事務的行,而id=4的行上面已經說過,而id=1的行由於刪除時間(刪除事務的ID)大於當前事務的ID,所以事務2的(2)select * from yang也會把id=1的數據檢索出來.所以,事務2中的兩條select 語句檢索出來的數據都如下:

id name 創建時間(事務ID) 刪除時間(事務ID)
1 yang 1 4
2 long 1 undefined
3 fei 1 undefined

UPDATE

InnoDB執行UPDATE,實際上是新插入了一行記錄,並保存其創建時間為當前事務的ID,同時保存當前事務ID到要UPDATE的行的刪除時間.

假設3

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

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

根據update的更新原則:會生成新的一行,並在原來要修改的列的刪除時間列上添加本事務ID,得到表如下:

id name 創建時間(事務ID) 刪除時間(事務ID)
1 yang 1 4
2 long 1 5
3 fei 1 undefined
4 tian 3 undefined
2 Long 5 undefined

繼續執行事務2的(2),根據select 語句的檢索條件,得到下表:

id name 創建時間(事務ID) 刪除時間(事務ID)
1 yang 1 4
2 long 1 5
3 fei 1 undefined

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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM