在上文中,咱們探討了MySQL不一樣存儲引擎中的各種鎖,在這篇文章中咱們將要討論的是MySQL是如何實現併發控制的。併發問題有三種,分別爲:html
首先咱們先來看一下悲觀鎖和樂觀鎖:git
悲觀鎖(Pessimistic Lock)其實是悲觀併發控制(Pessimistic Concurrency Control,「PCC」),顧名思義,就是對併發問題持有悲觀的態度,認爲每次對數據的操做都會引起併發衝突,所以悲觀鎖每次操做數據的時候都會上鎖,以屏蔽一切可能違反數據完整性的操做。github
悲觀鎖實際上就是運用了上一篇中提到的各種鎖來實現併發控制,較爲簡單,可是開銷比較大,並且只支持讀-讀併發,即對於同一個數據來講,若是採用悲觀鎖,那麼讀-寫和寫-寫併發是不被容許的。數據庫
樂觀鎖(Optimistic Lock)其實是樂觀併發控制(Optimistic Concurrency Control,「OCC」),對併發問題持有樂觀的態度,認爲不會發生併發衝突,所以不會上鎖(因此樂觀鎖並非一種鎖)。可是,在提交更新的時候會判斷一下在事務期間是否有其餘進程更新了同一塊數據。樂觀鎖解決了寫-寫衝突的無鎖併發控制(注意,這邊的無鎖並非真正的無鎖,而是在執行過程當中不加鎖,在檢測是否衝突的時候仍是須要對數據進行加鎖,可是這邊加鎖的時間明顯少了不少)。樂觀鎖通常來講有如下兩種實現方式:併發
1.使用數據版本(Version)記錄機制實現,這是樂觀鎖最經常使用的一種實現方式。何謂數據版本?即爲數據增長一個版本標識,通常是經過爲數據庫表增長一個數字類型的 「version」 字段來實現。當讀取數據時,將version字段的值一同讀出,數據每更新一次,對此version值加一。當咱們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的version值進行比對,若是數據庫表當前版本號與第一次取出來的version值相等,則予以更新,不然認爲是過時數據。用下面的一張圖來講明:mvc
如上圖所示,若是更新操做順序執行,則數據的版本(version)依次遞增,不會產生衝突。可是若是發生有不一樣的業務操做對同一版本的數據進行修改,那麼,先提交的操做(圖中B)會把數據version更新爲2,當A在B以後提交更新時發現數據的version已經被修改了,那麼A的更新操做會失敗。htm
2.使用時間戳(timestamp)。樂觀鎖定的第二種實現方式和第一種差很少,一樣是在須要樂觀鎖控制的table中增長一個字段,名稱無所謂,字段類型使用時間戳(timestamp), 和上面的version相似,也是在更新提交的時候檢查當前數據庫中數據的時間戳和本身更新前取到的時間戳進行對比,若是一致則OK,不然就是版本衝突。blog
從上文能夠看到:進程
1. 當發生併發衝突的機率比較大時,悲觀鎖更合適,以提升事務的成功率。事務
2. 當發生併發衝突的機率小時(如讀多寫少),樂觀鎖更合適,能夠提升系統的吞吐量。
接下來咱們來看看MVCC是什麼。
MVCC的意思爲多版本併發控制(Multiversion concurrency control),它解決的是讀-寫併發的問題。MVCC通常來講也能夠當作是一種樂觀機制,和間隙鎖同樣,它能夠用來解決幻讀的問題,只是間隙鎖解決幻讀是用使寫進程阻塞的方式來進行的,而MVCC是以快照的方式來處理這一問題。不一樣數據庫版本對MVCC的實現機制不一樣,在這邊咱們討論InnoDB是如何進行MVCC的。
InnoDb 會爲每一行記錄增長兩個字段,當前行建立時的版本號和刪除時的版本號(能夠爲空),事務在寫一條記錄時會將其拷貝一份生成這條記錄的一個原始拷貝,寫操做一樣仍是會對原記錄加鎖,可是讀操做會讀取未加鎖的新記錄,這就保證了讀寫並行。MVCC具體操做以下:
SELECT:InnoDB會根據如下兩個條件檢查每行記錄:
1)InnoDB只查找版本早於當前事務版本的數據行(也就是,行的系統版本號小於或等於事務的系統版本號),這樣能夠確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的。
2)行的刪除版本要麼未定義,要麼大於當前事務版本號。這能夠確保事務讀取到的行,在事務開始以前未被刪除。
INSERT:InnoDB爲新插入的每一行保存當前系統版本號做爲行版本號。
DELETE:InnoDB爲刪除的每一行保存當前系統版本號做爲行刪除標識。
UPDATE:InnoDB爲插入一行新記錄,保存當前系統版本號做爲行版本號,同時保存當系統的版本號爲原來的行做爲刪除標識。
InnoDb 經過 MVCC 實現了讀寫並行,可是在不一樣的隔離級別下,讀的方式也是有所區別的。首先要特別指出的是,在 read uncommit 隔離級別下,每次都是讀取最新版本的數據行,因此不能用 MVCC 的多版本,而 serializable 隔離級別每次讀取操做都會爲記錄加上讀鎖,也和 MVCC 不兼容,因此只有 RC 和 RR 這兩個隔離級別纔有 MVCC。
儘管 RR 和 RC 隔離級別都實現了 MVCC 來知足讀寫並行,可是讀的實現方式是不同的:RC 老是讀取記錄的最新版本,若是該記錄被鎖住,則讀取該記錄最新的一次快照,而 RR 是讀取該記錄事務開始時的那個版本。雖然這兩種讀取方式不同,可是它們讀取的都是快照數據,並不會被寫操做阻塞,因此這種讀操做稱爲 快照讀(Snapshot Read)。快照讀在InnoBD的實現中就是普通不加鎖的select語句。與快照讀相對應的是當前讀,即處理的都是當前的數據,須要加鎖,如select ... lock in share mode,for update以及select,update和delete,在解決當前讀的幻讀問題時,MySQL使用了間隙鎖的機制。
參考文檔:
https://liuzhengyang.github.io/2017/04/18/innodb-mvcc/
http://www.cnblogs.com/chenpingzhao/p/5065316.html
https://riverdba.github.io/2017/04/01/MVCC-theory-study/