1 什么是MVCC
MVCC全稱是: Multiversion concurrency control,多版本并發(fā)控制,提供并發(fā)訪問數(shù)據(jù)庫時,對事務(wù)內(nèi)讀取的到的內(nèi)存做處理,用來避免寫操作堵塞讀操作的并發(fā)問題。
舉個例子,程序員A正在讀數(shù)據(jù)庫中某些內(nèi)容,而程序員B正在給這些內(nèi)容做修改(假設(shè)是在一個事務(wù)內(nèi)修改,大概持續(xù)10s左右),A在這10s內(nèi) 則可能看到一個不一致的數(shù)據(jù),在B沒有提交前,如何讓A能夠一直讀到的數(shù)據(jù)都是一致的呢?
有幾種處理方法,第一種: 基于鎖的并發(fā)控制,程序員B開始修改數(shù)據(jù)時,給這些數(shù)據(jù)加上鎖,程序員A這時再讀,就發(fā)現(xiàn)讀取不了,處于等待情況,只能等B操作完才能讀數(shù)據(jù),這保證A不會讀到一個不一致的數(shù)據(jù),但是這個會影響程序的運行效率。還有一種就是:MVCC,每個用戶連接數(shù)據(jù)庫時,看到的都是某一特定時刻的數(shù)據(jù)庫快照,在B的事務(wù)沒有提交之前,A始終讀到的是某一特定時刻的數(shù)據(jù)庫快照,不會讀到B事務(wù)中的數(shù)據(jù)修改情況,直到B事務(wù)提交,才會讀取B的修改內(nèi)容。
一個支持MVCC的數(shù)據(jù)庫,在更新某些數(shù)據(jù)時,并非使用新數(shù)據(jù)覆蓋舊數(shù)據(jù),而是標記舊數(shù)據(jù)是過時的,同時在其他地方新增一個數(shù)據(jù)版本。因此,同一份數(shù)據(jù)有多個版本存儲,但只有一個是最新的。
MVCC提供了 時間一致性的 處理思路,在MVCC下讀事務(wù)時,通常使用一個時間戳或者事務(wù)ID來確定訪問哪個狀態(tài)的數(shù)據(jù)庫及哪些版本的數(shù)據(jù)。讀事務(wù)跟寫事務(wù)彼此是隔離開來的,彼此之間不會影響。假設(shè)同一份數(shù)據(jù),既有讀事務(wù)訪問,又有寫事務(wù)操作,實際上,寫事務(wù)會新建一個新的數(shù)據(jù)版本,而讀事務(wù)訪問的是舊的數(shù)據(jù)版本,直到寫事務(wù)提交,讀事務(wù)才會訪問到這個新的數(shù)據(jù)版本。
MVCC有兩種實現(xiàn)方式,第一種實現(xiàn)方式是將數(shù)據(jù)記錄的多個版本保存在數(shù)據(jù)庫中,當這些不同版本數(shù)據(jù)不再需要時,垃圾收集器回收這些記錄。這個方式被PostgreSQL和Firebird/Interbase采用,SQL Server使用的類似機制,所不同的是舊版本數(shù)據(jù)不是保存在數(shù)據(jù)庫中,而保存在不同于主數(shù)據(jù)庫的另外一個數(shù)據(jù)庫tempdb中。第二種實現(xiàn)方式只在數(shù)據(jù)庫保存最新版本的數(shù)據(jù),但是會在使用undo時動態(tài)重構(gòu)舊版本數(shù)據(jù),這種方式被Oracle和MySQL/InnoDB使用。
2、InnoDB的MVCC實現(xiàn)機制
MVCC可以認為是行級鎖的一個變種,它可以在很多情況下避免加鎖操作,因此開銷更低。MVCC的實現(xiàn)大都都實現(xiàn)了非阻塞的讀操作,寫操作也只鎖定必要的行。InnoDB的MVCC實現(xiàn),是通過保存數(shù)據(jù)在某個時間點的快照來實現(xiàn)的。一個事務(wù),不管其執(zhí)行多長時間,其內(nèi)部看到的數(shù)據(jù)是一致的。也就是事務(wù)在執(zhí)行的過程中不會相互影響。下面我們簡述一下MVCC在InnoDB中的實現(xiàn)。
InnoDB的MVCC,通過在每行記錄后面保存兩個隱藏的列來實現(xiàn):一個保存了行的創(chuàng)建時間,一個保存行的過期時間(刪除時間),當然,這里的時間并不是時間戳,而是系統(tǒng)版本號,每開始一個新的事務(wù),系統(tǒng)版本號就會遞增。在RR隔離級別下,MVCC的操作如下:
select操作。
InnoDB只查找版本早于(包含等于)當前事務(wù)版本的數(shù)據(jù)行??梢源_保事務(wù)讀取的行,要么是事務(wù)開始前就已存在,或者事務(wù)自身插入或修改的記錄。
行的刪除版本要么未定義,要么大于當前事務(wù)版本號??梢源_保事務(wù)讀取的行,在事務(wù)開始之前未刪除。
insert操作。將新插入的行保存當前版本號為行版本號。
delete操作。將刪除的行保存當前版本號為刪除標識。
update操作。變?yōu)閕nsert和delete操作的組合,insert的行保存當前版本號為行版本號,delete則保存當前版本號到原來的行作為刪除標識。
由于舊數(shù)據(jù)并不真正的刪除,所以必須對這些數(shù)據(jù)進行清理,innodb會開啟一個后臺線程執(zhí)行清理工作,具體的規(guī)則是將刪除版本號小于當前系統(tǒng)版本的行刪除,這個過程叫做purge。
3、簡單的小例子
create table yang(
id int primary key auto_increment,
name varchar(20));
}
假設(shè)系統(tǒng)的版本號從1開始.
INSERT
InnoDB為新插入的每一行保存當前系統(tǒng)版本號作為版本號.
第一個事務(wù)ID為1;
start transaction;
insert into yang values(NULL,'yang') ;
insert into yang values(NULL,'long');
insert into yang values(NULL,'fei');
commit;
對應在數(shù)據(jù)中的表如下(后面兩列是隱藏列,我們通過查詢語句并看不到)
SELECT
InnoDB會根據(jù)以下兩個條件檢查每行記錄:
a.InnoDB只會查找版本早于當前事務(wù)版本的數(shù)據(jù)行(也就是,行的系統(tǒng)版本號小于或等于事務(wù)的系統(tǒng)版本號),這樣可以確保事務(wù)讀取的行,要么是在事務(wù)開始前已經(jīng)存在的,要么是事務(wù)自身插入或者修改過的.
b.行的刪除版本要么未定義,要么大于當前事務(wù)版本號,這可以確保事務(wù)讀取到的行,在事務(wù)開始之前未被刪除.
只有a,b同時滿足的記錄,才能返回作為查詢結(jié)果.
DELETE
InnoDB會為刪除的每一行保存當前系統(tǒng)的版本號(事務(wù)的ID)作為刪除標識.
看下面的具體例子分析:
第二個事務(wù),ID為2;
start transaction;
select * from yang; //(1)
select * from yang; //(2)
commit;
假設(shè)1
假設(shè)在執(zhí)行這個事務(wù)ID為2的過程中,剛執(zhí)行到(1),這時,有另一個事務(wù)ID為3往這個表里插入了一條數(shù)據(jù);
第三個事務(wù)ID為3;
start transaction;
insert into yang values(NULL,'tian');
commit;
這時表中的數(shù)據(jù)如下:
然后接著執(zhí)行事務(wù)2中的(2),由于id=4的數(shù)據(jù)的創(chuàng)建時間(事務(wù)ID為3),執(zhí)行當前事務(wù)的ID為2,而InnoDB只會查找事務(wù)ID小于等于當前事務(wù)ID的數(shù)據(jù)行,所以id=4的數(shù)據(jù)行并不會在執(zhí)行事務(wù)2中的(2)被檢索出來,在事務(wù)2中的兩條select 語句檢索出來的數(shù)據(jù)都只會下表:
假設(shè)2
假設(shè)在執(zhí)行這個事務(wù)ID為2的過程中,剛執(zhí)行到(1),假設(shè)事務(wù)執(zhí)行完事務(wù)3后,接著又執(zhí)行了事務(wù)4;
第四個事務(wù):
start transaction;
delete from yang where id=1;
commit;
此時數(shù)據(jù)庫中的表如下:
接著執(zhí)行事務(wù)ID為2的事務(wù)(2),根據(jù)SELECT 檢索條件可以知道,它會檢索創(chuàng)建時間(創(chuàng)建事務(wù)的ID)小于當前事務(wù)ID的行和刪除時間(刪除事務(wù)的ID)大于當前事務(wù)的行,而id=4的行上面已經(jīng)說過,而id=1的行由于刪除時間(刪除事務(wù)的ID)大于當前事務(wù)的ID,所以事務(wù)2的(2)select * from yang也會把id=1的數(shù)據(jù)檢索出來.所以,事務(wù)2中的兩條select 語句檢索出來的數(shù)據(jù)都如下:
UPDATE
InnoDB執(zhí)行UPDATE,實際上是新插入了一行記錄,并保存其創(chuàng)建時間為當前事務(wù)的ID,同時保存當前事務(wù)ID到要UPDATE的行的刪除時間.
假設(shè)3
假設(shè)在執(zhí)行完事務(wù)2的(1)后又執(zhí)行,其它用戶執(zhí)行了事務(wù)3,4,這時,又有一個用戶對這張表執(zhí)行了UPDATE操作:
第5個事務(wù):
start transaction;
update yang set name='Long' where id=2;
commit;
根據(jù)update的更新原則:會生成新的一行,并在原來要修改的列的刪除時間列上添加本事務(wù)ID,得到表如下:
繼續(xù)執(zhí)行事務(wù)2的(2),根據(jù)select 語句的檢索條件,得到下表:
還是和事務(wù)2中(1)select 得到相同的結(jié)果.
到此這篇關(guān)于mysql的MVCC多版本并發(fā)控制的實現(xiàn)的文章就介紹到這了,更多相關(guān)mysql MVCC多版本并發(fā)控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- MySQL系列之十 MySQL事務(wù)隔離實現(xiàn)并發(fā)控制
- 詳解MySQL多版本并發(fā)控制機制(MVCC)源碼
- MySQL高并發(fā)生成唯一訂單號的方法實現(xiàn)
- MySQL 加鎖控制并發(fā)的方法
- Mysql事務(wù)并發(fā)問題解決方案
- MySQL 數(shù)據(jù)庫如何解決高并發(fā)問題
- mysql并發(fā)控制原理知識點
- mysql多版本并發(fā)控制MVCC的實現(xiàn)
- MySQL并發(fā)更新數(shù)據(jù)時的處理方法
- Tomcat+Mysql高并發(fā)配置優(yōu)化講解
- MySQL 到底是如何做到多版本并發(fā)的?