MySQL-InnoDB-MVCC多版本併發控制

(Multiversion Concurrency Control)html

前言

最近正在啃《高性能MySQL》這本書, 當看到事務相關知識時,決定對該知識點稍微深刻一下, 《高性能MySQL》中在介紹事務相關知識點時, 顯然不是特別深刻, 不少比較底層的知識點並無太多的深刻, 固然此處並非要對本書作什麼評判,言歸正傳, 這裏主要先說一下本人在啃相關知識點時的曲折之路:mysql

  1. 首先是事務相關ACID特性, 以前已經有相關筆記進行過介紹, 這裏再也不重複;
  2. 接下來是高併發事務相關的問題, 像是 髒讀, 不可重複讀, 幻讀, 更新丟失等問題以前也有相關筆記;
  3. 再下來就是MySQL應對高併發事務是如何給出解決方案的(其中包含各個隔離級別的簡介);
  4. 而後就是各個隔離級別的具體介紹及與鎖的關係, 也就是在這部分知識點, 發現了以前並無過多關心的知識點 MVCC多版本併發控制, 而後一發不可收拾了...

入題

下面先引用一些前輩們比較優秀的文章:git

阿里數據庫內核'2017/12'月報中對MVCC的解釋是:
多版本控制: 指的是一種提升併發的技術。最先的數據庫系統,只有讀讀之間能夠併發,讀寫,寫讀,寫寫都要阻塞。引入多版本以後,只有寫寫之間相互阻塞,其餘三種操做均可以並行,這樣大幅度提升了InnoDB的併發度。在內部實現中,與Postgres在數據行上實現多版本不一樣,InnoDB是在undolog中實現的,經過undolog能夠找回數據的歷史版本。找回的數據歷史版本能夠提供給用戶讀(按照隔離級別的定義,有些讀請求只能看到比較老的數據版本),也能夠在回滾的時候覆蓋數據頁上的數據。在InnoDB內部中,會記錄一個全局的活躍讀寫事務數組,其主要用來判斷事務的可見性。

<高性能MySQL>中對MVCC的部分介紹github

  • MySQL的大多數事務型存儲引擎實現的其實都不是簡單的行級鎖。基於提高併發性能的考慮, 它們通常都同時實現了多版本併發控制(MVCC)。不只是MySQL, 包括Oracle,PostgreSQL等其餘數據庫系統也都實現了MVCC, 但各自的實現機制不盡相同, 由於MVCC沒有一個統一的實現標準。
  • 能夠認爲MVCC是行級鎖的一個變種, 可是它在不少狀況下避免了加鎖操做, 所以開銷更低。雖然實現機制有所不一樣, 但大都實現了非阻塞的讀操做,寫操做也只鎖定必要的行。
  • MVCC的實現方式有多種, 典型的有樂觀(optimistic)併發控制 和 悲觀(pessimistic)併發控制。
  • MVCC只在 READ COMMITTEDREPEATABLE READ 兩個隔離級別下工做。其餘兩個隔離級別夠和MVCC不兼容, 由於 READ UNCOMMITTED 老是讀取最新的數據行, 而不是符合當前事務版本的數據行。而 SERIALIZABLE 則會對全部讀取的行都加鎖。

從書中能夠了解到:算法

  • MVCC是被Mysql中 事務型存儲引擎InnoDB 所支持的;
  • 應對高併發事務, MVCC比單純的加鎖更高效;
  • MVCC只在 READ COMMITTEDREPEATABLE READ 兩個隔離級別下工做;
  • MVCC可使用 樂觀(optimistic)鎖悲觀(pessimistic)鎖來實現;
  • 各數據庫中MVCC實現並不統一
  • 可是書中提到 "InnoDB的MVCC是經過在每行記錄後面保存兩個隱藏的列來實現的"(網上也有不少此類觀點), 但其實並不許確, 能夠參考MySQL官方文檔, 能夠看到, InnoDB存儲引擎在數據庫每行數據的後面添加了三個字段, 不是兩個!!

相關概念

1.read view, 快照snapshotsql

淘寶數據庫內核月報/2017/10/01/
此文雖然是以PostgreSQL進行的說明, 但並不影響理解, 在"事務快照的實現"該部分有細節須要注意:
事務快照是用來存儲數據庫的事務運行狀況。一個事務快照的建立過程能夠歸納爲:
查看當前全部的未提交併活躍的事務,存儲在數組中
選取未提交併活躍的事務中最小的XID,記錄在快照的xmin中
選取全部已提交事務中最大的XID,加1後記錄在xmax中

注意: 上文中在PostgreSQL中snapshot的概念, 對應MySQL中, 其實就是你在網上看到的read view,快照這些概念;
好比何登成就有關於Read view的介紹;
此文 卻還是使用快照來介紹;數據庫

2.read view 主要是用來作可見性判斷的, 比較廣泛的解釋即是"本事務不可見的當前其餘活躍事務", 但正是該解釋, 可能會形成一節理解上的誤區, 因此此處提供兩個參考, 供給你們避開理解誤區:數組

read view中的`高水位low_limit_id`能夠參考 https://github.com/zhangyachen/zhangyachen.github.io/issues/68, https://www.zhihu.com/question/66320138
其實上面第1點中加粗部分也是相關高水位的介紹( 注意進行了+1 )

3.另外, 對於read view快照的生成時機, 也很是關鍵, 正是由於生成時機的不一樣, 形成了RC,RR兩種隔離級別的不一樣可見性;併發

  • 在innodb中(默認repeatable read級別), 事務在begin/start transaction以後的第一條select讀操做後, 會建立一個快照(read view), 將當前系統中活躍的其餘事務記錄記錄起來;
  • 在innodb中(默認repeatable committed級別), 事務中每條select語句都會建立一個快照(read view);
  • 參考
With REPEATABLE READ isolation level, the snapshot is based on the time when the first read operation is performed.
 使用REPEATABLE READ隔離級別,快照是基於執行第一個讀操做的時間。
With READ COMMITTED isolation level, the snapshot is reset to the time of each consistent read operation.
使用READ COMMITTED隔離級別,快照被重置爲每一個一致的讀取操做的時間。

4.undo-log高併發

  • Undo log是InnoDB MVCC事務特性的重要組成部分。當咱們對記錄作了變動操做時就會產生undo記錄,Undo記錄默認被記錄到系統表空間(ibdata)中,但從5.6開始,也可使用獨立的Undo 表空間。
  • Undo記錄中存儲的是老版本數據,當一箇舊的事務須要讀取數據時,爲了能讀取到老版本的數據,須要順着undo鏈找到知足其可見性的記錄。當版本鏈很長時,一般能夠認爲這是個比較耗時的操做(例如bug#69812)。
  • 大多數對數據的變動操做包括INSERT/DELETE/UPDATE,其中INSERT操做在事務提交前只對當前事務可見,所以產生的Undo日誌能夠在事務提交後直接刪除(誰會對剛插入的數據有可見性需求呢!!),而對於UPDATE/DELETE則須要維護多版本信息,在InnoDB裏,UPDATE和DELETE操做產生的Undo日誌被歸成一類,即update_undo
  • 另外, 在回滾段中的undo logs分爲: insert undo logupdate undo log

    • insert undo log : 事務對insert新記錄時產生的undolog, 只在事務回滾時須要, 而且在事務提交後就能夠當即丟棄。
    • update undo log : 事務對記錄進行delete和update操做時產生的undo log, 不只在事務回滾時須要, 一致性讀也須要,因此不能隨便刪除,只有當數據庫所使用的快照中不涉及該日誌記錄,對應的回滾日誌纔會被purge線程刪除。

5.InnoDB存儲引擎在數據庫每行數據的後面添加了三個字段

  • 6字節的事務ID(DB_TRX_ID)字段: 用來標識最近一次對本行記錄作修改(insert|update)的事務的標識符, 即最後一次修改(insert|update)本行記錄的事務id。
    至於delete操做,在innodb看來也不過是一次update操做,更新行中的一個特殊位將行表示爲deleted, 並不是真正刪除
  • 7字節的回滾指針(DB_ROLL_PTR)字段: 指寫入回滾段(rollback segment)的 undo log record (撤銷日誌記錄記錄)。
    若是一行記錄被更新, 則 undo log record 包含 '重建該行記錄被更新以前內容' 所必須的信息。
  • 6字節的DB_ROW_ID字段: 包含一個隨着新行插入而單調遞增的行ID, 當由innodb自動產生彙集索引時,彙集索引會包括這個行ID的值,不然這個行ID不會出如今任何索引中。
    結合聚簇索引的相關知識點, 個人理解是, 若是咱們的表中沒有主鍵或合適的惟一索引, 也就是沒法生成聚簇索引的時候, InnoDB會幫咱們自動生成彙集索引, 但聚簇索引會使用DB_ROW_ID的值來做爲主鍵; 若是咱們有本身的主鍵或者合適的惟一索引, 那麼聚簇索引中也就不會包含 DB_ROW_ID 了 。
    關於聚簇索引, 《高性能MySQL》中的篇幅對我來講已經夠用了, 稍後會整理一下之前的學習筆記, 而後更新上來。

6.可見性比較算法(這裏每一個比較算法後面的描述是創建在rr級別下,rc級別也是使用該比較算法,此處未作描述)
設要讀取的行的最後提交事務id(即當前數據行的穩定事務id)爲 trx_id_current
當前新開事務id爲 new_id
當前新開事務建立的快照read view 中最先的事務id爲up_limit_id, 最遲的事務id爲low_limit_id(注意這個low_limit_id=未開啓的事務id=當前最大事務id+1)
比較:

  • 1.trx_id_current < up_limit_id, 這種狀況比較好理解, 表示, 新事務在讀取該行記錄時, 該行記錄的穩定事務ID是小於, 系統當前全部活躍的事務, 因此當前行穩定數據對新事務可見, 跳到步驟5.
  • 2.trx_id_current >= trx_id_last, 這種狀況也比較好理解, 表示, 該行記錄的穩定事務id是在本次新事務建立以後纔開啓的, 可是卻在本次新事務執行第二個select前就commit了,因此該行記錄的當前值不可見, 跳到步驟4。
  • 3.trx_id_current <= trx_id_current <= trx_id_last, 表示: 該行記錄所在事務在本次新事務建立的時候處於活動狀態,從up_limit_id到low_limit_id進行遍歷,若是trx_id_current等於他們之中的某個事務id的話,那麼不可見, 調到步驟4,不然表示可見。
  • 4.從該行記錄的 DB_ROLL_PTR 指針所指向的回滾段中取出最新的undo-log的版本號, 將它賦值該 trx_id_current,而後跳到步驟1從新開始判斷。
  • 5.將該可見行的值返回。

案例分析

  1. 下面是一個很是簡版的演示事務對某行記錄的更新過程, 固然, InnoDB引擎在內部要作的工做很是多:
    clipboard.png
  2. 下面是一套比較算法的應用過程, 比較長
    比較算法

當前讀和快照讀

1.MySQL的InnoDB存儲引擎默認事務隔離級別是RR(可重複讀), 是經過 "行排他鎖+MVCC" 一塊兒實現的, 不只能夠保證可重複讀, 還能夠部分防止幻讀, 而非徹底防止;

2.爲何是部分防止幻讀, 而不是徹底防止?

  • 效果: 在若是事務B在事務A執行中, insert了一條數據並提交, 事務A再次查詢, 雖然讀取的是undo中的舊版本數據(防止了部分幻讀), 可是事務A中執行update或者delete都是能夠成功的!!
  • 由於在innodb中的操做能夠分爲當前讀(current read)快照讀(snapshot read):

3.快照讀(snapshot read)

簡單的select操做(固然不包括 select ... lock in share mode, select ... for update)

4.當前讀(current read) 官網文檔 Locking Reads

  • select ... lock in share mode
  • select ... for update
  • insert
  • update
  • delete

在RR級別下,快照讀是經過MVVC(多版本控制)和undo log來實現的,當前讀是經過加record lock(記錄鎖)和gap lock(間隙鎖)來實現的。
innodb在快照讀的狀況下並無真正的避免幻讀, 可是在當前讀的狀況下避免了不可重複讀和幻讀!!!

小結

  1. 通常咱們認爲MVCC有下面幾個特色:

    • 每行數據都存在一個版本,每次數據更新時都更新該版本
    • 修改時Copy出當前版本, 而後隨意修改,各個事務之間無干擾
    • 保存時比較版本號,若是成功(commit),則覆蓋原記錄, 失敗則放棄copy(rollback)
    • 就是每行都有版本號,保存時根據版本號決定是否成功,聽起來含有樂觀鎖的味道, 由於這看起來正是,在提交的時候才能知道到底可否提交成功
  2. 而InnoDB實現MVCC的方式是:

    • 事務以排他鎖的形式修改原始數據
    • 把修改前的數據存放於undo log,經過回滾指針與主數據關聯
    • 修改爲功(commit)啥都不作,失敗則恢復undo log中的數據(rollback)
  3. 兩者最本質的區別是: 當修改數據時是否要排他鎖定,若是鎖定了還算不算是MVCC?
  • Innodb的實現真算不上MVCC, 由於並無實現核心的多版本共存, undo log 中的內容只是串行化的結果, 記錄了多個事務的過程, 不屬於多版本共存。但理想的MVCC是難以實現的, 當事務僅修改一行記錄使用理想的MVCC模式是沒有問題的, 能夠經過比較版本號進行回滾, 但當事務影響到多行數據時, 理想的MVCC就無能爲力了。
  • 好比, 若是事務A執行理想的MVCC, 修改Row1成功, 而修改Row2失敗, 此時須要回滾Row1, 但由於Row1沒有被鎖定, 其數據可能又被事務B所修改, 若是此時回滾Row1的內容,則會破壞事務B的修改結果,致使事務B違反ACID。 這也正是所謂的 第一類更新丟失 的狀況。
  • 也正是由於InnoDB使用的MVCC中結合了排他鎖, 不是純的MVCC, 因此第一類更新丟失是不會出現了, 通常說更新丟失都是指第二類丟失更新。
相關文章
相關標籤/搜索