InnoDB-MVCC與樂觀鎖

最近通過《高性能MySQL》一書學習MySQL方面的知識,在看到書中所講InnoDB-MVCC部分的時候,有一種強烈的感覺,這不就是樂觀鎖嗎(入門級小學徒的疑惑臉)?當下便去網上以各種方式查找閱讀MVCC和樂觀鎖相關的博客,發現大部分的博客對於這兩者之間的關係都隻字不提,提了的也是衆說紛紜,關於兩者關係的細節方面也十分曖昧沒有定論。在暫時無法得出最終結論的情況下,我先談談在學習這方面知識後我自己對兩者的理解,然後試着得出自己的結論,正確與否大家一起思考。

在解釋MVCC之前,我首先引用《高性能MySQL》書中原文來解釋一下隔離級別:

  • READ UNCOMMITTED(未提交讀):在READ UNCOMMITTED級別,事務中的修改,即使沒有提交,對其他事務也都是可見的。事務可以讀取未提交的數據,這也被稱爲髒讀

  • READ COMMITTED(提交讀):大多數數據庫默認的隔離級別(MySQL除外)。在READ COMMITTED級別,一個事務開始時,只能“看見”已經提交的事務所做的修改。換句話說,一個事務從開始直到提交之前,所做的任何修改對其他事務都是不可見的,這個級別有時候也叫做不可重複讀,因爲同一事務中兩次執行同樣的查詢,可能會得到不一樣的結果。

  • REPEATABLE READ(可重複讀):這是MySQL默認的隔離級別,解決了髒讀的問題。該級別保證了在同一事務中多次讀取同樣記錄的結果是一致的。但是理論上,該隔離級別還是無法解決另外一個幻讀的問題。所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另外一個事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會產生幻行

  • SERIALIZABLE(可串行化):該隔離級別下通過強制事務串行執行,避免了幻讀的問題。簡單來說,SERIALIZABLE會在讀取的每一行數據上都加鎖,所以可能導致大量的超時和鎖爭用的問題。

如果看了以上對四個隔離級別的解釋還是無法理解什麼是隔離級別以及爲什麼要有隔離級別,可以去網上百度隔離級別,網上有一些通過現實場景來解釋隔離級別的例子很容易理解,這裏就不再做贅述。

那隔離級別到底和MVCC有什麼關係呢?如果說將數據庫比作一輛汽車,然後將隔離級別比作汽車輪轂,那麼MVCC就是ABS防抱死制動系統,不過這個ABS防抱死制動系統只適用於READ COMMITTED和REPEATABLE READ兩個型號的輪轂。

這裏寫圖片描述

上面解釋隔離級別時提到了,在REPEATABLE READ隔離級別下,儘管解決了不可重複讀,但還是存在幻讀的問題。如果要避免幻讀,就得在事務執行的時候加鎖,但是大量的鎖會嚴重影響性能。怎樣才能不通過加鎖還能解決幻讀呢?這就是MVCC要做的事情。

MVCC是Multi-Version Concurrency Control(多版本併發控制)的縮寫,很多數據庫都實現了MVCC,但是在不同的存儲引擎中MVCC的實現是不同的,今天所說的是InnoDB中的MVCC實現。InnoDB的MVCC,是通過在每行記錄後保存兩個隱藏的列來實現的(用戶不可見)。一個列保存行創建的時間,一個列保存行過期(刪除)的時間,這裏所說的時間並不是傳統意義上的時間,而是系統版本號,下面是REPEATABLE READ隔離級別下MVCC的具體操作:
-SELECT
InnoDB會根據以下兩個條件檢查每行記錄:
(1)InnoDB只查找版本早於當前事務版本的數據行(行的系統版本號小於或者等於事務的系統版本號),這樣可以確保事務讀取到的行,要麼是在事務開始之前已經存在的,要麼是事務自身插入或者修改過的(結合以下INSERT、UPDATE操作理解)。
(2)行的刪除版本要麼未定義,要麼大於當前事務版本號。可以確保事務讀取到的行,在事務開啓之前未被刪除(結合以下DELETE操作理解)。
-INSERT
InnoDB爲新插入的每一行保存當前系統版本號作爲行版本號。
-DELETE
InnoDB爲刪除的每一行保存當前系統版本號作爲行刪除標識(第二個隱藏列的作用來了)。
-UPDATE
InnoDB將更新後的列作爲新的行插入數據庫(並不是覆蓋),並保存當前系統版本號作爲該行的行版本號,同時保存當前系統版本號到原來的行作爲行刪除標識。

到這裏,MVCC是什麼以及它做了什麼事基本上已經說清楚了,爲什麼在學習了MVCC後我會產生“這就是樂觀鎖”的想法呢(實際上很多人都有這種想法,在一些博客裏也有人說MVCC就是樂觀鎖)?有這幾個原因。首先,InnoDB中MVCC和樂觀鎖(其實這麼說是不嚴謹的,後面會解釋爲什麼)都是通過“不加鎖”的手段來實現加鎖的效果。其次它們的不加鎖手段都是通過版本號去控制的。通過這兩點也不難看出爲什麼會有很多人在MVCC和樂觀鎖之間產生疑問。

那麼樂觀鎖是怎麼實現的呢?最常見的就是通過數據版本(Version)記錄機制實現。數據版本和InnoDB-MVCC中的系統版本作用相似不做過多解釋。通過爲數據庫表增加一個數字類型的字段作爲版本標識Version(用戶可見,字段名自定),當讀取數據時,將其Version的值一同讀出,數據每更新一次,Version都增加1,當提交更新的時候,判斷數據庫表對應行的當前版本信息與第一次讀取出來的Version值進行對比,如果一致,則給與更新,否則不予更新(可以不涉及事務,但是MVCC機制必須依託於事務,事實上隔離級別本就是事務的隔離級別)。具體操作如下:

SELECT id, name, Version FROM testable;(例如id=1,Version=1024)
UPDATE testable SET name=’張三’,Version=Version+1 WHERE id=1 AND Version=1024;

這裏寫圖片描述 這裏寫圖片描述

從上面的所有文字中,我們還是無法得出一個有效的結論,只看得出InnoDB-MVCC和文中所提到的樂觀鎖確實很像,它們到底是何關係我們還是無從所知。那我們再來看看《高性能MySQL》中所提到的一句話:不同存儲引擎的MVCC實現是不同的,典型的有樂觀併發控制和悲觀併發控制。看完這句話我們再結合上文,可以得出這樣一個結論:MVCC並不是樂觀鎖,InnoDB所實現的MVCC纔是樂觀鎖(當然也有其他存儲引擎利用樂觀併發控制的思想實現MVCC),更嚴謹一點來說,樂觀鎖並不是一種具體的技術,樂觀鎖只是一種併發控制的思想,所有認爲“併發事務不算大”而採用非加鎖的形式來實現“加鎖”效果的控制機制我們都認爲它是樂觀鎖。既然如此,那我們之前提到的樂觀鎖就不能叫樂觀鎖了,它只是樂觀鎖的一種表達方式,是一種在用戶行爲上通過非加鎖的方式來實現併發控制的手段。同樣的MVCC更不能稱之爲樂觀鎖,只能說InnoDB實現的MVCC是一種在系統行爲上通過非加鎖的方式來實現併發控制的手段。

總結來說,InnoDB-MVCC是一種系統行爲,在REPEATABLE READ隔離級別下,它通過樂觀併發控制解決了該隔離級別所不能解決的幻讀,但是前提是這些都得依託於事務的封裝。儘管如此,它還是無法完全解決一些併發業務場景下的問題,並且過多的事務使用會嚴重影響系統的性能,這就需要通過用戶行爲去約束(最開始所提到的樂觀鎖)。

所以,最後的結論就是,MVCC並非樂觀鎖,但是InnoDB存儲引擎所實現的MVCC是樂觀的,它和之前所提到的用戶行爲的“樂觀鎖”都採用的是樂觀機制,屬於不同的“樂觀鎖”手段,它們都是“樂觀家族”的成員。

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