mysql的MVCC多版本並發控制的實現
1 什麼是MVCC
MVCC全稱是: Multiversion concurrency control,多版本並發控制,提供並發訪問數據庫時,對事務內讀取的到的內存做處理,用來避免寫操作堵塞讀操作的並發問題。
舉個例子,程序員A正在讀數據庫中某些內容,而程序員B正在給這些內容做修改(假設是在一個事務內修改,大概持續10s左右),A在這10s內 則可能看到一個不一致的數據,在B沒有提交前,如何讓A能夠一直讀到的數據都是一致的呢?
有幾種處理方法,第一種: 基於鎖的並發控制,程序員B開始修改數據時,給這些數據加上鎖,程序員A這時再讀,就發現讀取不瞭,處於等待情況,隻能等B操作完才能讀數據,這保證A不會讀到一個不一致的數據,但是這個會影響程序的運行效率。還有一種就是:MVCC,每個用戶連接數據庫時,看到的都是某一特定時刻的數據庫快照,在B的事務沒有提交之前,A始終讀到的是某一特定時刻的數據庫快照,不會讀到B事務中的數據修改情況,直到B事務提交,才會讀取B的修改內容。
一個支持MVCC的數據庫,在更新某些數據時,並非使用新數據覆蓋舊數據,而是標記舊數據是過時的,同時在其他地方新增一個數據版本。因此,同一份數據有多個版本存儲,但隻有一個是最新的。
MVCC提供瞭 時間一致性的 處理思路,在MVCC下讀事務時,通常使用一個時間戳或者事務ID來確定訪問哪個狀態的數據庫及哪些版本的數據。讀事務跟寫事務彼此是隔離開來的,彼此之間不會影響。假設同一份數據,既有讀事務訪問,又有寫事務操作,實際上,寫事務會新建一個新的數據版本,而讀事務訪問的是舊的數據版本,直到寫事務提交,讀事務才會訪問到這個新的數據版本。
MVCC有兩種實現方式,第一種實現方式是將數據記錄的多個版本保存在數據庫中,當這些不同版本數據不再需要時,垃圾收集器回收這些記錄。這個方式被PostgreSQL和Firebird/Interbase采用,SQL Server使用的類似機制,所不同的是舊版本數據不是保存在數據庫中,而保存在不同於主數據庫的另外一個數據庫tempdb中。第二種實現方式隻在數據庫保存最新版本的數據,但是會在使用undo時動態重構舊版本數據,這種方式被Oracle和MySQL/InnoDB使用。
2、InnoDB的MVCC實現機制
MVCC可以認為是行級鎖的一個變種,它可以在很多情況下避免加鎖操作,因此開銷更低。MVCC的實現大都都實現瞭非阻塞的讀操作,寫操作也隻鎖定必要的行。InnoDB的MVCC實現,是通過保存數據在某個時間點的快照來實現的。一個事務,不管其執行多長時間,其內部看到的數據是一致的。也就是事務在執行的過程中不會相互影響。下面我們簡述一下MVCC在InnoDB中的實現。
InnoDB的MVCC,通過在每行記錄後面保存兩個隱藏的列來實現:一個保存瞭行的創建時間,一個保存行的過期時間(刪除時間),當然,這裡的時間並不是時間戳,而是系統版本號,每開始一個新的事務,系統版本號就會遞增。在RR隔離級別下,MVCC的操作如下:
select操作。
InnoDB隻查找版本早於(包含等於)當前事務版本的數據行。可以確保事務讀取的行,要麼是事務開始前就已存在,或者事務自身插入或修改的記錄。
行的刪除版本要麼未定義,要麼大於當前事務版本號。可以確保事務讀取的行,在事務開始之前未刪除。
insert操作。將新插入的行保存當前版本號為行版本號。
delete操作。將刪除的行保存當前版本號為刪除標識。
update操作。變為insert和delete操作的組合,insert的行保存當前版本號為行版本號,delete則保存當前版本號到原來的行作為刪除標識。
由於舊數據並不真正的刪除,所以必須對這些數據進行清理,innodb會開啟一個後臺線程執行清理工作,具體的規則是將刪除版本號小於當前系統版本的行刪除,這個過程叫做purge。
3、簡單的小例子
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;
對應在數據中的表如下(後面兩列是隱藏列,我們通過查詢語句並看不到)
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;
這時表中的數據如下:
然後接著執行事務2中的(2),由於id=4的數據的創建時間(事務ID為3),執行當前事務的ID為2,而InnoDB隻會查找事務ID小於等於當前事務ID的數據行,所以id=4的數據行並不會在執行事務2中的(2)被檢索出來,在事務2中的兩條select 語句檢索出來的數據都隻會下表:
假設2
假設在執行這個事務ID為2的過程中,剛執行到(1),假設事務執行完事務3後,接著又執行瞭事務4;
第四個事務:
start transaction; delete from yang where id=1; commit;
此時數據庫中的表如下:
接著執行事務ID為2的事務(2),根據SELECT 檢索條件可以知道,它會檢索創建時間(創建事務的ID)小於當前事務ID的行和刪除時間(刪除事務的ID)大於當前事務的行,而id=4的行上面已經說過,而id=1的行由於刪除時間(刪除事務的ID)大於當前事務的ID,所以事務2的(2)select * from yang也會把id=1的數據檢索出來.所以,事務2中的兩條select 語句檢索出來的數據都如下:
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,得到表如下:
繼續執行事務2的(2),根據select 語句的檢索條件,得到下表:
還是和事務2中(1)select 得到相同的結果.
到此這篇關於mysql的MVCC多版本並發控制的實現的文章就介紹到這瞭,更多相關mysql MVCC多版本並發控制內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- None Found