高性能MySQL學習筆記(5) —— MVCC

多版本控制

  Multi-Version Concurrency Control,多版本控制,每次操作,copy一份所要改的數據作爲副本,副本之間通過一個版本號字段區分,並將副本的版本號+1,如果是更新操作,數據在副本上修改完後,要更新時候查看原紀錄的版本號是否是副本版本號-1,是,更新,否(說明有其他修改事務在這期間修改了數據,使其版本號更新了),失敗,重新取數據重新更新;如果是讀操作,則是根據隔離級別讀取小於等於當前事務版本號數據庫數據(並不更改版本號)。
  上面的邏輯只是粗略說明MVCC是一個怎麼樣的東西,實際各個存儲引擎的實現並不一定就這樣,事實上,MVCC只是一個標準說明,而沒有說明具體的實現。
  另外,有關事務隔離級別詳見:事務隔離級別

理論

  高性能MySQL一書上說的MVCC實現方式:
  InnoDB的MVCC:通過給每條記錄後面保存兩個隱藏的字段來實現:一個是行的創建時間,一個是行的刪除時間。當然,實際上存的不是時間值,而是系統版本號,每開始一個新的事務,系統版本號會自動遞增。事務開始的版本號則是系統版本號,用來和查詢到的每行記錄的版本號作比較。下面是隔離級別爲Repeatable read下MVCC具體操作:

SELECT
Innodb檢查沒行數據,確保他們符合兩個標準:
  1、InnoDB只查找版本早於當前事務版本的數據行(也就是數據行的版本必須小於等於事務的版本),這確保當前事務讀取的行都是事務之前已經存在的,或者是由當前事務創建或修改的行。
  2、行的刪除操作的版本一定是未定義的或者大於當前事務的版本號。確保了當前事務開始之前,行沒有被刪除(2)。
  符合了以上兩點則返回查詢結果。
INSERT
  InnoDB新插入的行以當前系統版本號爲行版本號。
DELETE
  InnoDB爲刪除的每一行保存當前系統版本號作爲刪除標識。
UPDATE
  InnoDB複製了一行。這個新行的版本號使用了系統版本號。它也把系統版本號作爲了刪除行的版本。
  上面最重要的是系統版本號一個概念,系統版本號其實就是這個表從創建到此時所經歷過的事務次數,開始爲0。注意系統版本號不是行版本號,表的每個行的版本號一般是不一樣的,因爲不是每次操作都是針對整個表的。有了這個概念下面就好理解了:
  一個事務過程:

//注意,以下只針對隔離級別爲Read commited和Repeatable read,其他隔離級別後面再說
0.事務開始,以下步驟數據操作並沒有寫到數據庫中
1.系統版本號+1
2.事務版本號=系統版本號
3.進行操作
    (1)讀操作:遍歷數據庫表,查找版本號<=事務版本號的行(這裏包括了複製的行,等於是
因爲可能這個事務對數據作了修改或增加),還有,這個時候可能有其他事務對數據做了修改,但
修改會改變行版本號(增加),選取<=,所以是看不見的(隔離級別控制),而若這個時候有其
他事務有增加新數據,同樣版本號>當前系統版本號,所以不會出現幻讀。
    (2)插入操作:增加一個新行,行版本號=系統版本號
    (3)刪除操作:將該行的刪除版本號(注意不是行版本號)=系統版本號,注意,這個時候並
沒有物理刪除,只是做了一個標記(如果最後不刪除,或則哪怕刪除,會存在類似undo.log文件
中,用來回滾)
    (4)更新操作:複製所更新行記錄,將原行進行刪除操作,新行版本號=系統版本號
4.提交事務(Commit),試圖更新數據:
    (0)系統版本號+1
    (1)對比數據行的副本與原數據,若副本的行版本號=原數據行版本號,更新成功,否則失敗,回滾操作。
    (2)若有行數據的刪除版本=當前系統版本號,刪除數據行。
5.操作成功,更新數據。

  再說下其他兩個隔離級別:Read commited和Serializable,這兩個級別下,MVCC是沒用的,因爲Read commited總是讀取最新的數據,而不是符合當前事務版本的數據行,而Serializable呢,則會使對當前所有讀取的行加鎖,所以MVCC完全沒用。

實現

  以上,其實說的還是理論上的東西,實際上的實現並不是完全如此,就比如第(4)點更新操作,是如何判斷複製行是哪條行的複製?所以還需要一個字段,事實上,MySQL也的確如此做的,每一行其實有增加三個隱藏字段:
  1. 6字節的事務ID(DB_TRX_ID ),這個就是系統版本號(也是行版本號)
  2. 7字節的回滾指針(DB_ROLL_PTR),這個就是刪除版本號,實現上是用來回滾,只有當undo.log緩存不足時才刪除。
  3. 隱藏的ID,這就是每條行的id,用以區分原紀錄與副本
  其中,7字節回滾指針有關InnoDB事務模型,我這裏簡單說兩個比較重要的概念:
  redo.log:重做日誌,就是每次mysql在執行寫入數據前先把要寫的信息保存在重寫日誌中,但出現斷電,奔潰,重啓等等導致數據不能正常寫入期望數據時,服務器可以通過redo.log中的信息重新寫入數據。
  undo log:撤銷日誌,與redo log恰恰相反,當一些更改在執行一半時,發生意外,而無法完成,則可以根據撤消日誌恢復到更改之前的狀態。
  具體詳情,這裏我自己並沒有深入研究,詳情可以參考:Mysql中的MVCC(老碼農的專欄)

MVCC與樂觀鎖

  搜索查了一下二者的關係,發現很多說法,衆說紛紜,這裏我個人覺得這兩者的關係是:
  二者均是概念上的思想,並不是實現,而二者也不是等於或包含被包含關係,因爲,樂觀鎖可以通過MVCC這種思路實現,也可以通過其他方法實現,比如CAS;但是MVCC本身只是想實現非阻塞的讀,可以認爲MVCC是行級鎖的一個變種,很多情況避免了加鎖操作,但不是沒有加鎖,寫操作是有加鎖的,只是加的是必要的行,事實上MVCC的實現有樂觀併發控制,也有悲觀併發控制的。

總結

  總的來說,MVCC就是用空間換時間,用複製一行來操作,而不是對行加鎖,減少了加鎖操作,之後查看原行是否在這期間有被更新過,若無操作成功,若有操作失敗,回滾重新操作。適用於單行操作,如果過多多行更新操作的話,失敗率很高,性能效率很低。

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