MVCC即Multi-Version Concurrency Control,中文翻譯過來叫多版本併發控制。sql
衆所周知,在MYSQL中,MyISAM使用的是表鎖,InnoDB使用的是行鎖。而InnoDB的事務分爲四個隔離級別,其中默認的隔離級別REPEATABLE READ須要兩個不一樣的事務相互之間不能影響,並且還能支持併發,這點悲觀鎖是達不到的,因此REPEATABLE READ採用的就是樂觀鎖,而樂觀鎖的實現採用的就是MVCC。正是由於有了MVCC,才造就了InnoDB強大的事務處理能力。數據庫
InnoDB的MVCC,是經過在每行記錄後面保存兩個隱藏的列來實現的,這兩個列,分別保存了這個行的建立時間,一個保存的是行的刪除時間。這裏存儲的並非實際的時間值,而是系統版本號(能夠理解爲事務的ID),每開始一個新的事務,系統版本號就會自動遞增,事務開始時刻的系統版本號會做爲事務的ID.下面看一下在REPEATABLE READ隔離級別下,MVCC具體是如何操做的。併發
首先建立一張表:翻譯
create table yang( id int primary key auto_increment, name varchar(20) );
假設系統的版本號從1開始.code
InnoDB爲新插入的每一行保存當前系統版本號做爲版本號。第一個事務ID爲1:事務
start transaction; insert into yang values(NULL,'yang'); insert into yang values(NULL,'long'); insert into yang values(NULL,'fei'); commit;
對應在數據中的表以下(後面兩列是隱藏列,咱們經過查詢語句並看不到)rem
id | name | 建立時間(事務ID) | 刪除時間(事務ID) |
---|---|---|---|
1 | yang | 1 | undefined |
2 | long | 1 | undefined |
3 | fei | 1 | undefined |
InnoDB會根據如下兩個條件檢查每行記錄:it
InnoDB只會查找版本早於當前事務版本的數據行(也就是,行的系統版本號小於或等於事務的系統版本號),這樣能夠確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的.io
行的刪除版本要麼未定義,要麼大於當前事務版本號(這能夠確保事務讀取到的行,在事務開始以前未被刪除),
只有條件一、2同時知足的記錄,才能返回做爲查詢結果.table
InnoDB會爲刪除的每一行保存當前系統的版本號(事務的ID)做爲刪除標識.
看下面的具體例子分析: 第二個事務,ID爲2:
start transaction; select * from yang; select * from yang; 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 |
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 獲得相同的結果.