MYSQL MVCC實現(xiàn)原理

MVCC(Multi Version Concurrency Control的簡稱),代表多版本并發(fā)控制。與MVCC相對的,是基于鎖的并發(fā)控制,Lock-Based Concurrency Control)。
MVCC最大的優(yōu)勢:讀不加鎖,讀寫不沖突。在讀多寫少的OLTP應(yīng)用中,讀寫不沖突是非常重要的,極大的增加了系統(tǒng)的并發(fā)性能

了解MVCC前,我們先學習下Mysql架構(gòu)和數(shù)據(jù)庫事務(wù)隔離級別

MYSQL 架構(gòu)

MySQL從概念上可以分為四層,頂層是接入層,不同語言的客戶端通過mysql的協(xié)議與mysql服務(wù)器進行連接通信,接入層進行權(quán)限驗證、連接池管理、線程管理等。下面是mysql服務(wù)層,包括sql解析器、sql優(yōu)化器、數(shù)據(jù)緩沖、緩存等。再下面是mysql中的存儲引擎層,mysql中存儲引擎是基于表的。最后是系統(tǒng)文件層,保存數(shù)據(jù)、索引、日志等。

事務(wù)隔離級別

大家都知道數(shù)據(jù)庫事務(wù)具備ACID特性,即Atomicity(原子性) Consistency(一致性), Isolation(隔離性), Durability(持久性)

原子性:要執(zhí)行的事務(wù)是一個獨立的操作單元,要么全部執(zhí)行,要么全部不執(zhí)行

一致性:事務(wù)的一致性是指事務(wù)的執(zhí)行不能破壞數(shù)據(jù)庫的一致性,一致性也稱為完整性。一個事務(wù)在執(zhí)行后,數(shù)據(jù)庫必須從一個一致性狀態(tài)轉(zhuǎn)變?yōu)榱硪粋€一致性狀態(tài)。

隔離性:多個事務(wù)并發(fā)執(zhí)行時,一個事務(wù)的執(zhí)行不應(yīng)影響其他事務(wù)的執(zhí)行,SQL92規(guī)范中對隔離性定義了不同的隔離級別:

讀未提交(READ UNCOMMITED)->讀已提交(READ COMMITTED)->可重復讀(REPEATABLE READ)->序列化(SERIALIZABLE)。隔離級別依次增強,但是導致的問題是并發(fā)能力的減弱。

隔離級別 臟讀 不可重復讀 幻讀 概念
READ UNCOMMITED 事務(wù)能夠看到其他事務(wù)沒有提交的修改,當另一個事務(wù)又回滾了修改后的情況,又被稱為臟讀dirty read
READ COMMITTED × 事務(wù)能夠看到其他事務(wù)提交后的修改,這時會出現(xiàn)一個事務(wù)內(nèi)兩次讀取數(shù)據(jù)可能因為其他事務(wù)提交的修改導致不一致的情況,稱為不可重復讀
REPEATABLE READ × × 事務(wù)在兩次讀取時讀取到的數(shù)據(jù)的狀態(tài)是一致的
SERIALIZABLE × × × 可重復讀中可能出現(xiàn)第二次讀讀到第一次沒有讀到的數(shù)據(jù),也就是被其他事務(wù)插入的數(shù)據(jù),這種情況稱為幻讀phantom read, 該級別中不能出現(xiàn)幻讀

大多數(shù)數(shù)據(jù)庫系統(tǒng)的默認隔離級別都是READ COMMITTED(但MySQL不是),InnoDB存儲引擎默認隔離級別REPEATABLE READ,通過多版本并發(fā)控制(MVCC,Multiversion Concurrency Control)解決了幻讀的問題。

MYSQL 事務(wù)日志

事務(wù)日志可以幫助提高事務(wù)的效率。使用事務(wù)日志,存儲引擎在修改表的數(shù)據(jù)時只需要修改其內(nèi)存拷貝,再把該修改行為記錄到持久在硬盤上的事務(wù)日志中,而不用每次都將修改的數(shù)據(jù)本身持久到磁盤。事務(wù)日志采用的是追加的方式,因此寫日志的操作是磁盤上一小塊區(qū)域內(nèi)的順序I/O,而不像隨機I/O需要在磁盤的多個地方移動磁頭,所以采用事務(wù)日志的方式相對來說要快得多。事務(wù)日志持久以后,內(nèi)存中被修改的數(shù)據(jù)在后臺可以慢慢地刷回到磁盤。目前大多數(shù)存儲引擎都是這樣實現(xiàn)的,我們通常稱之為預寫式日志(Write-Ahead Logging),修改數(shù)據(jù)需要寫兩次磁盤。
如果數(shù)據(jù)的修改已經(jīng)記錄到事務(wù)日志并持久化,但數(shù)據(jù)本身還沒有寫回磁盤,此時系統(tǒng)崩潰,存儲引擎在重啟時能夠自動恢復這部分修改的數(shù)據(jù)。

MySQL Innodb中跟數(shù)據(jù)持久性、一致性有關(guān)的日志,有以下幾種:

  • Bin Log:是mysql服務(wù)層產(chǎn)生的日志,常用來進行數(shù)據(jù)恢復、數(shù)據(jù)庫復制,常見的mysql主從架構(gòu),就是采用slave同步master的binlog實現(xiàn)的
  • Redo Log:記錄了數(shù)據(jù)操作在物理層面的修改,mysql中使用了大量緩存,修改操作時會直接修改內(nèi)存,而不是立刻修改磁盤,事務(wù)進行中時會不斷的產(chǎn)生redo log,在事務(wù)提交時進行一次flush操作,保存到磁盤中。當數(shù)據(jù)庫或主機失效重啟時,會根據(jù)redo log進行數(shù)據(jù)的恢復,如果redo log中有事務(wù)提交,則進行事務(wù)提交修改數(shù)據(jù)。
  • Undo Log: 除了記錄redo log外,當進行數(shù)據(jù)修改時還會記錄undo log,undo log用于數(shù)據(jù)的撤回操作,它記錄了修改的反向操作,比如,插入對應(yīng)刪除,修改對應(yīng)修改為原來的數(shù)據(jù),通過undo log可以實現(xiàn)事務(wù)回滾,并且可以根據(jù)undo log回溯到某個特定的版本的數(shù)據(jù),實現(xiàn)MVCC

MVCC實現(xiàn)

MVCC是通過在每行記錄后面保存兩個隱藏的列來實現(xiàn)的。這兩個列,一個保存了行的創(chuàng)建時間,一個保存行的過期時間(或刪除時間)。當然存儲的并不是實際的時間值,而是系統(tǒng)版本號(system version number)。每開始一個新的事務(wù),系統(tǒng)版本號都會自動遞增。事務(wù)開始時刻的系統(tǒng)版本號會作為事務(wù)的版本號,用來和查詢到的每行記錄的版本號進行比較。
下面看一下在REPEATABLE READ隔離級別下,MVCC具體是如何操作的。

  • SELECT

    InnoDB會根據(jù)以下兩個條件檢查每行記錄:

    1. InnoDB只查找版本早于當前事務(wù)版本的數(shù)據(jù)行(也就是,行的系統(tǒng)版本號小于或等于事務(wù)的系統(tǒng)版本號),這樣可以確保事務(wù)讀取的行,要么是在事務(wù)開始前已經(jīng)存在的,要么是事務(wù)自身插入或者修改過的。
    2. 行的刪除版本要么未定義,要么大于當前事務(wù)版本號。這可以確保事務(wù)讀取到的行,在事務(wù)開始之前未被刪除。

    只有符合上述兩個條件的記錄,才能返回作為查詢結(jié)果

  • INSERT

    InnoDB為新插入的每一行保存當前系統(tǒng)版本號作為行版本號。

  • DELETE

    InnoDB為刪除的每一行保存當前系統(tǒng)版本號作為行刪除標識。

  • UPDATE

    InnoDB為插入一行新記錄,保存當前系統(tǒng)版本號作為行版本號,同時保存當前系統(tǒng)版本號到原來的行作為行刪除標識。
    保存這兩個額外系統(tǒng)版本號,使大多數(shù)讀操作都可以不用加鎖。這樣設(shè)計使得讀數(shù)據(jù)操作很簡單,性能很好,并且也能保證只會讀取到符合標準的行,不足之處是每行記錄都需要額外的存儲空間,需要做更多的行檢查工作,以及一些額外的維護工作

舉例說明

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

transaction 1:

start transaction;
insert into mvcctest values(NULL,'mi');
insert into mvcctest values(NULL,'kong');
commit;

假設(shè)系統(tǒng)初始事務(wù)ID為1;

ID NAME 創(chuàng)建時間 過期時間
1 mi 1 undefined
2 kong 1 undefined

transaction 2:

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

SELECT

假設(shè)當執(zhí)行事務(wù)2的過程中,準備執(zhí)行語句(2)時,開始執(zhí)行事務(wù)3:

transaction 3:

start transaction;
insert into mvcctest values(NULL,'qu');
commit;
ID NAME 創(chuàng)建時間 過期時間
1 mi 1 undefined
2 kong 1 undefined
3 qu 3 undefined

事務(wù)3執(zhí)行完畢,開始執(zhí)行事務(wù)2 語句2,由于事務(wù)2只能查詢創(chuàng)建時間小于等于2的,所以事務(wù)3新增的記錄在事務(wù)2中是查不出來的,這就通過樂觀鎖的方式避免了幻讀的產(chǎn)生

UPDATE

假設(shè)當執(zhí)行事務(wù)2的過程中,準備執(zhí)行語句(2)時,開始執(zhí)行事務(wù)4:

transaction session 4:

start transaction;
update mvcctest set name = 'fan' where id = 2;
commit;

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

ID NAME 創(chuàng)建時間 過期時間
1 mi 1 undefined
2 kong 1 4
2 fan 4 undefined

事務(wù)4執(zhí)行完畢,開始執(zhí)行事務(wù)2 語句2,由于事務(wù)2只能查詢創(chuàng)建時間小于等于2的,所以事務(wù)修改的記錄在事務(wù)2中是查不出來的,這樣就保證了事務(wù)在兩次讀取時讀取到的數(shù)據(jù)的狀態(tài)是一致的

DELETE

假設(shè)當執(zhí)行事務(wù)2的過程中,準備執(zhí)行語句(2)時,開始執(zhí)行事務(wù)5:

transaction session 5:

start transaction;
delete from mvcctest where id = 2;
commit;
ID NAME 創(chuàng)建時間 過期時間
1 mi 1 undefined
2 kong 1 5

事務(wù)5執(zhí)行完畢,開始執(zhí)行事務(wù)2 語句2,由于事務(wù)2只能查詢創(chuàng)建時間小于等于2、并且過期時間大于等于2,所以id=2的記錄在事務(wù)2 語句2中,也是可以查出來的,這樣就保證了事務(wù)在兩次讀取時讀取到的數(shù)據(jù)的狀態(tài)是一致的

參考:

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

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

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