MVVC與樂觀鎖和悲觀鎖

在併發讀寫數據庫時,讀操做可能會不一致的數據(髒讀)。爲了不這種狀況,須要實現數據庫的併發訪問控制,最簡單的方式就是加鎖訪問。因爲,加鎖會將讀寫操做串行化,因此不會出現不一致的狀態。可是,讀操做會被寫操做阻塞,大幅下降讀性能。在java concurrent包中,有copyonwrite系列的類,專門用於優化讀遠大於寫的狀況。而其優化的手段就是,在進行寫操做時,將數據copy一份,不會影響原有數據,而後進行修改,修改完成後原子替換掉舊的數據,而讀操做只會讀取原有數據。經過這種方式實現寫操做不會阻塞讀操做,從而優化讀效率。而寫操做之間是要互斥的,而且每次寫操做都會有一次copy,因此只適合讀大於寫的狀況。java

MVCC的原理與copyonwrite相似,全稱是Multi-Version Concurrent Control,即多版本併發控制。在MVCC協議下,每一個讀操做會看到一個一致性的snapshot,而且能夠實現非阻塞的讀。MVCC容許數據具備多個版本,這個版本能夠是時間戳或者是全局遞增的事務ID,在同一個時間點,不一樣的事務看到的數據是不一樣的。mysql

實現原理: sql

------------------------------------------------------------------------------------------> 時間軸數據庫

|-------R(T1)-----|併發

|-----------U(T2)-----------|post

如上圖,假設有兩個併發操做R(T1)和U(T2),T1和T2是事務ID,T1小於T2,系統中包含數據a = 1(T1),R和W的操做以下:性能

R:read a (T1)優化

U:a = 2    (T2)spa

R(讀操做)的版本T1表示要讀取數據的版本,而以後寫操做纔會更新版本,讀操做不會。在時間軸上,R晚於U,而因爲U在R開始以後提交,因此對於R是不可見的。因此,R只會讀取T1版本的數據,即a = 1。線程

因爲在update操做提交以前,不能影響已有數據的一致性,因此不會改變舊的數據,update操做會被拆分紅insert + delete。須要標記刪除舊的數據,insert新的數據。只有update提交以後,纔會影響後續的讀操做。而對於讀操做並且,只能讀到在其以前的全部的寫操做,正在執行中的寫操做對其是不可見的。

上面說了一堆的虛的理論,下面來點幹活,看一下mysql的innodb引擎是如何實現MVCC的。innodb會爲每一行添加兩個字段,分別表示該行建立的版本刪除的版本,填入的是事務的版本號,這個版本號隨着事務的建立不斷遞增。innodb MVCC主要是爲Repeatable-Read事務隔離級別作的。在此隔離級別下,A、B客戶端所示的數據相互隔離,互相更新不可見,在Repeatable-Read的隔離級別下,具體各類數據庫操做的實現:

SELECT

  InnoDB會根據如下兩個條件檢查每行記錄:

  一、InnoDB只查找版本小於或等於當前事務版本的數據行,這樣能夠確保事務讀取的行,是在事務開始前就已經存在的,或者是事務自身插入或者修改過的數據。

  二、行的刪除版本要麼未定義,要麼大於當前事務的版本。這能夠確保事務讀取到的行,在事務開始前未被刪除。

  只有符合上述兩個條件的記錄,才能返回作爲查詢結果。

INSERT

  InnoDB爲新插入的每一行保存當前系統版本號做爲行版本號。

DELETE

  InnoDB爲刪除的每一行保存當前系統版本號做爲行刪除標識。

UPDATE

  InnoDB爲插入一行新記錄,保存當前系統版本號做爲行版本號,同時保存當前系統版本號到原來的行做爲刪除標識。

其中,寫操做(insert、delete和update)執行時,須要將系統版本號遞增。

因爲舊數據並不真正的刪除,因此必須對這些數據進行清理,innodb會開啓一個後臺線程執行清理工做,具體的規則是將刪除版本號小於當前系統版本的行刪除,這個過程叫作purge。

經過MVCC很好的實現了事務的隔離性,能夠達到repeated read級別,要實現serializable還必須加鎖。

優缺點:

  保存這兩個額外的系統版本號,使大多數讀操做均可以不用加鎖。這樣設計使得讀數據操做很簡單,性能很好。而且也能保證只會讀取到符合標準的行。不足之處是每行記錄都須要額外的存儲空間,須要作更多的檢查工做,以及一些額外的維護工做。

innodb 和postgre實現:

  • postgres 是嚴格地無鎖,對寫操做也是樂觀併發控制;在表中保存同一行數據記錄的多個不一樣版本,每次寫操做,都是建立,而回避更新;在事務提交時,按版本號檢查當前事務提交的數據是否存在寫衝突,則拋異常告知用戶,回滾事務;
  • innodb 則只對讀無鎖,寫操做還是上鎖的悲觀併發控制,這也意味着,innodb 中只能見到因死鎖和不變性約束而回滾,而見不到由於寫衝突而回滾;不像 postgres 那樣對數據修改在表中建立新紀錄,而是每行數據只在表中保留一份,在更新數據時上行鎖,同時將舊版數據寫入 undo log;表和 undo log 中行數據都記錄着事務ID,在檢索時,只讀取來自當前已提交的事務的行數據;

MVCC有效範圍:

  MVCC只在REPEATABLE READ(可重複讀)和READ COMMITTED(提交讀)兩個隔離級別下工做。其餘兩個隔離級別都和MVCC不兼容。READ UNCOMMITTED(未提交讀)老是讀取最新的數據行,而SERIALIZBLE(可串行化)則會對事務串行化執行,即對錶加鎖。
相關文章
相關標籤/搜索