深入簡出的掌握InnoDB引擎 MVCC協議

MVCC(Multi-Version Concurrent Control),即多版本併發控制協議,廣泛使用於數據庫系統(mysql、HBase)。由於MVCC沒有一個統一的實現標準,本人將針對mysql的InnoDB引擎談談它的應用。

1.是什麼

MVCC是行鎖的一種變種,但是他在很多情況下避免了加鎖的操作,因此開銷更低。多版本併發控制實現了非阻塞的讀操作,也可稱之爲一致性非鎖定讀。它通過行的多版本控制方式來讀取當前執行時間數據庫中的行數據。實質上使用的是快照數據

2.爲什麼需要

  • 消除鎖的開銷;如果要保證數據的一致性,最簡單的方式就是對操作數據進行加鎖,但是加鎖不可避免的會有鎖開銷。所以,如果有能避免進行加鎖的方式當然是最好的。
  • 提高併發度,後面會重點介紹。

3.它與事務隔離級別的關聯

事務隔離級別“讀已提交”與“可重複讀”下 ,InnoDB存儲引擎使用MVCC機制,

在“讀已提交”事務隔離級別下,對於快照數據,MVCC讀總是讀取被鎖定行的最新的快照數據

而“可重複讀”讀到的總是讀取事務開始時的行數據版本

4.原理介紹

對於“可重複讀”讀到的總是讀取事務開始時的行數據版本,那MVCC機制是如何保證可重複讀的?

還要從它的數據結構上談起,

1.行記錄,Innodb引擎會爲每一行添加3個字段實現的,DATA_TRX_ID、DATA_ROLL_PTR與DELETED_BIT

DATA _TRX_ID 表示產生當前記錄項的事務ID(每開啓一個新的事務,其對應的事務id會自動遞增)
DATA_ROLL_PTR 一個指向此條記錄項的undo信息的指針,undo信息是指此條記錄被修改前的信息;
DELETED_BIT 用於標識該記錄是否被刪除

2.read view

它很重要,innodb的read view確定一條記錄能否看到,它遵循的原則:

  • 看不到read view創建時刻以後啓動的事務,場景1,Rule 1: When the read view object is created it notes down the smallest transaction identifier that is not yet used as a transaction identifier (trx_sys_t::max_trx_id).   The read view calls it the low limit. So the transaction using the read view must not see any transaction with identifier greater than or equal to this low limit.
  • 看不到read view創建時活躍的事務,場景2,Rule 2: The transaction using the read view must not see a transaction that was active when the read view was created.

RR(REPEATABLE-READ)讀:每個事務在開始都會根據當前系統的活躍事務鏈表創建一個read_view,read_view是用來檢索行的可見性的。
RC(READ-COMMITTED)讀:事務中,每個語句都會創建read_view。

---------------------

Read View中的的變量(當前的活躍事務鏈表current-trx —> trx7 —> trx5 —> trx3 —> trx1)則按如下方式初始化:

read_view->creator_trx_id = current-trx;                       當前的事務id
read_view->up_limit_id = trx1;                                      當前活躍事務的最小id
read_view->low_limit_id = trx7;                                     當前活躍事務的最大id
read_view->trx_ids = [trx1, trx3, trx5, trx7];                   當前活躍的事務的id列表
read_view->m_trx_ids = 4;                                            當前活躍的事務id列表長度

---------------------

low_limit_id,即當時活躍事務的最大id,如果讀到row的data_trx_id>=low_limit_id,說明這些數據在當前事務開始時都還沒有提交,如註釋中的描述,這些數據都不可見。

up_limit_id,即當時活躍事務列表的最小事務id,如果row的data_trx_id<up_limit_id,說明這些數據在當前事務開始時都已經提交,如註釋中的描述,這些數據均可見。

data_trx_id在up_limit_id和low_limit_id之間的row,如果這個data_trx_id在trx_ids的集合中,就說明開啓當前的事務的時候,這個data_trx_id還處於活躍狀態,即還未提交,那麼這個row是不可見的;如果這個data_trx_id不在trx_ids的集合中,就說明開啓當前的事務的時候,這個data_trx_id已經提交,那麼這個row是可見的。

實例:

 CREATE TABLE `test` (  
  `id` int(11) NOT NULL,  
  `b` int(11) NOT NULL,  
  PRIMARY KEY (`id`)  
) ENGINE=InnoDB 

表中有數據(1,1)

autocommit爲false, tx_isolation 是REPEATABLE-READ

場景1:

session A

session B

start transaction;(A)

 

 

start transaction

 

update test set b=2 where id=1;

 

commit;

select * from test;(B)

 

B處結果爲(1,2),似乎不符合那段話裏的a條件,A事務看到了transaction version更大的B事務

場景2:

session A  

  session B

                          

start transaction

                         

update test set b=2 where id=1;

start transaction

 

select * from test;

 

                           

commit;

select * from test;(C)

 

C處結果爲(1,1),也就是說,A事務沒有看到transaction version更小的B事務

5.缺點

爲了實現多版本,innodb必須對每行增加相應的字段來存儲版本信息,同時需要維護每一行的版本信息,而且在檢索行的時候,需要進行版本的比較,因而降低了查詢的效率;innodb還必須定期清理不再需要的行版本,及時回收空間,這也增加了一些開銷

6.爲什麼select count(*)在myisam表上很快,而在Innodb的表上很慢?

因爲innodb採用了MVCC技術,對於相同的行,可能同時存在多個版本,innodb必須根據查詢的時間來過濾掉一些行,才能得出結果,必然要執行全表掃描,而全表掃描是非常耗時的.對於myisam的表,任何行都只有一個版本,mysql甚至不需要掃描就可以直接返回精確的統計結果,我們用explain也可以看到,對於myisam的表,執行select count(*)的時候,mysql顯示” Select tables optimized away”,查詢直接被優化了;而對於innodb的表,可能是全表掃描,也可能是”using index”,總之,速度肯定會比myisam的錶慢很多.

7.能禁用MVCC嗎?

禁用MVCC可以降低innodb引擎的開銷,而同時innodb又可以支持外鍵約束,可以實現自動恢復.MVCC本身不支持read uncommitted等級,所以可以通過設置transaction_isolation = read uncommitted 來禁用MVCC.但是任何改變innodb默認隔離等級的操作,都會起到innodb_locks_unsafe_for_binlog=off類似的效果,這會導致諸如insert into t select * from t_src 之類的語句不再給源表t_src加鎖,也不再使用innodb的間隙鎖,從而產生幻讀,直接導致binlog中記錄的sql語句不能正確的串行化,從而主從數據庫的數據不再一致,而且基於binlog的增量備份也不再有效.所以除非不需要記錄binlog,否則別這麼做.當然我們可以這樣做來優化從庫的性能,因爲從庫不需要記錄binlog.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章