一文帶你看懂 InnoDB 中的 MVCC、Undo、Redo 機制


寫在開頭

  接上一篇博客:InnoDB 事務與鎖的前世今生,本文是在 InnoDB 併發事務與鎖內容之後,再來分析 InnoDB 引擎針對併發事務數據一致性操作的詳細介紹。

  本文將從:1.什麼是 MVCC?  2.MVCC 在 MySQL 中的體現   3.Undo Log   4.Redo Log 這些方面來詳細的介紹。通過案例、插圖,以最通俗易懂的方式,讓你徹底掌握 MVCC 的來龍去脈。

Tips:本文以 MySQ L默認事務隔離級別 REPEATABLE READ(可重複讀)進行介紹。

1.一題引出MVCC

  上一篇博客中,我們已經詳細介紹了共享鎖排它鎖意向鎖自增鎖臨鍵鎖間隙鎖記錄鎖這些內容。我們就接着這些鎖來分析下面這兩個示例。
在這裏插入圖片描述
  以上兩個示例,最終返回查詢結果均是1。疑惑的是,已經+X鎖了,爲什麼還是能夠查詢成功呢?在此處就引出了 MVCC這個概念。雖然這兩種情況看着查詢結果一致,但是 InnoDB 底層實現卻是完全不同的,它們底層的實現與 MVCC 又有什麼關係呢?接下來就帶你進入 MVCC 的世界,領略 InnoDB 引擎的牛X之處。

2.什麼是 MVCC

  MVCC,全稱 Multi-Version Concurrency Control(多版本併發控制)。MVCC 是一種併發控制的方法,一般在數據庫管理系統中,實現對數據庫的併發訪問;在編程語言中實現事務內存。(本段摘自百度百科:MVCC)

  簡單理解爲:併發訪問數據庫時(讀和寫),對正在事務內處理的數據做多版本的管理,以達到用來 避免寫操作的阻塞,從而提升讀操作的併發問題 (基於undo log 快照讀來解決)

  (可能還不是很懂,繼續往下看,案例圖解帶你繼續分析↓↓↓)

3.MVCC 在 MySQL 中的體現

  我們在數據庫創建表時,比如有idnameage三個字段,但是 InnoDB 的內部實現中爲每一行數據增加了三個隱藏列用於實現 MVCC。

列名 長度(字節) 作用
DB_TRX_ID 6 插入或更新行的最後一個事務的事務標識符(版本號)。(刪除視爲更新,將其標記爲已刪除)
DB_ROLL_PTR 7 寫入回滾段的撤消日誌事務標識符(版本號)(若行已更新,則撤消日誌記錄包含在更新行之前重建行內容所需的信息)
DB_ROW_ID 6 行標識(自增id,也就是當我們建表沒有指定主鍵列時,mysql會自動生成DB_ROW_ID用作主鍵)

所以,我們創建表的真正結構,其實是下面這個樣子的:

id name age DB_ROW_ID DB_TRX_ID DB_ROLL_PTR

  InnoDB引擎,通過爲每一行記錄添加兩個額外的隱藏的值( DB_TRX_IDDB_ROLL_PTR )來實現MVCC,這兩個值一個記錄這行數據何時被創建,另外一個記錄這行數據何時過期(或者被刪除)。並不會存儲這些事件發生時的實際時間,相反它只存儲這些事件發生時的系統版本號。這是一個隨着事務的創建而不斷增長的數字。每個事務在事務開始時會記錄它自己的系統版本號。下文中,我們就只拿這兩個字段+事務版本號來做介紹了。

3.1 插入流程

  MVCC 在 MySQL 執行插入操作時,InnoDB 會爲這個新的數據行,DB_TRX_ID 字段記錄當前的事務版本號。執行流程圖如下所示:
在這裏插入圖片描述

3.2 刪除流程

  MVCC 在 MySQL 執行刪除操作時,InnoDB 會爲要刪除的這個行,DB_ROLL_PTR 字段記錄當前刪除(回滾)的事務版本號。執行流程圖如下所示:
在這裏插入圖片描述

3.3 修改流程

  MVCC 在 MySQL 執行修改操作時,InnoDB 會寫一個這行數據的新拷貝,這個拷貝的版本號爲當前的事務版本號(DB_TRX_ID 字段);同時它會將這個事務版本號寫到舊行的刪除(回滾)版本號中( DB_ROLL_PTR 字段)。執行流程圖如下所示:
在這裏插入圖片描述

3.4 查詢流程

MVCC 在 MySQL 執行查詢操作時,InnoDB 查詢數據有兩條規則:

  1. 查找 數據行事務版本號 <= 當前事務ID 的數據行;
  2. 查找 刪除(回滾)版本號爲 NULL 或者 刪除(回滾)版本號大於當前事務ID 的數據行。

執行流程圖如下所示:
在這裏插入圖片描述

4. 使用 MVCC 後對示例分析

  有兩個事務1、2 對數據進行操作。①②③④爲執行步驟序號
在這裏插入圖片描述

4.1 情形一

  按照步驟 ①②③④②執行操作。流程圖如下所示:在這裏插入圖片描述

4.2 情形二

  按照步驟 ③④①②執行操作。流程圖如下所示:在這裏插入圖片描述

4.3 情形分析

  我們發現,兩個事務A、B 在兩種情形下執行相同的操作,但是查詢卻出現了不同的結果。那麼這問題出在了哪裏?我們繼續向下分析。其實這問題並不是出在 MVCC 上,針對這種情況,InnoDB 引擎有事怎麼解決這一問題的呢??這就引出了:undo Log、redo Log 這兩個概念。

5. Undo Log

  在 Undo Log 之前,我們遇到兩個問題加 X 鎖寫操作阻塞時,那麼數據庫在高併發時肯定時不行的; 使用 MVCC 控制版本時,又發現一個重複讀導致的數據不一致問題。此時:InnoDB 引擎就是通過引入 undo log ,通過都快照的方式來解決這兩個問題的。

  undo log 的出現,既可以主要解決數據原子性問題的同時,又可以配合 MVCC 間接解決高併發下的寫操作阻塞問題。

5.1 什麼是 undo log

  undo log 是指在事務開始之前、操作數據之前,首先將需要操作的數據備份到一個地方的過程。undo log 的出現,是爲了 實現事務的原子性 而出現的產物。

5.2 undo 如何實現事務原子性

   事務在處理的過程中,如果出現了錯誤用戶執行了 rollback。MySQL 可以利用 undo log 中的備份,將數據恢復到事務開始之前的狀態。

  在 InnoDB 引擎中,undo log 和 MVCC 一起來實現 MySQL 數據的多版本控制。 在事務提交之前,undo 保存了未提交事務之前的版本數據到 undo log,undo log 中的數據可以作爲數據舊版本快照來供其他併發事務進行快照讀。

5.3 快照讀、當前讀

  基於 undo log,可以將 undo log 中的數據作爲舊版本快照的方式,來供其他併發事務進行快照讀。(這就是 MVCC 配合 undo log 來解決讀操作的併發問題)

Ⅰ.快照讀 (通過MVCC + undo log 解決幻讀問題)

  SQL 讀取的數據時快照版本,也就是歷史版本。普通的 Select 就是基於快照讀的方式 。InnoDB 快照讀,數據的讀取將由 cache(原本數據) + undo(事務修改過的數據)兩部分組成。

Ⅱ.當前讀 (通過Next-key lock 解決幻讀問題)

  SQL 讀取的數據時最新版本。通過 鎖機制 來保證讀取的數據無法通過其他事務進行修改。常用的 updatedeleteinsertselect xxx LOCK IN SHARE MODE(共享鎖)select xxx FOR UPDATE(排他鎖) 都是當前讀。

5.4 情形二基於 undo log 再做分析

瞭解了 undo log 之後,我們 基於 undo log + MVCC 再來按步驟 ③④①② 分析執行結果,圖示如下:
在這裏插入圖片描述
執行流程:

  1. 執行第④步,update user set age = 18 where id = 1; 會將 user 表中數據備份到 undo buffer 緩存;
  2. update 後,如果事務2執行 rollback 操作,那麼 use 表會基於 undo buffer 數據回滾到 update 之前狀態;(這就是 undo 爲了解決事務原子性而出現的產物)
  3. 既然已經將舊數據進行備份,InnoDB 引擎就將 undo buffer 當做一個快照表來進行讀取;
  4. 執行 select age from user where id = 1; 查詢時。便會從快照表中讀取數據;(這就是爲什麼 update 加了 X 鎖,還能讀取數據的原因)
  5. undo buffer 是在 cache 緩存中,最終它裏面的數據還是會 flush 到 disk 磁盤上的。(undo buffer 是一個緩衝區,是有一定的大小的,如果達到指定大小,事務還未commit/rollback,此時就會寫磁盤了)

5.5 硬盤中的 undo log 文件有什麼用

  如果當前事務在執行過程中, MySQL 服務崩潰了,重啓時則會從 undo log 日誌中恢復;此時如果斷電了,重啓後還是會根據 undo 日誌進行恢復。此操作具有冪等性,重複多少次都沒有問題

6. Redo Log

6.1 什麼是 redo log

  redo log 是指事務中操作的任何數據,將最新的數據備份到一個地方的過程。redo log是爲了實現事務的持久性而出現的產物。 redo log 的持久性,並不是隨着事務的提交才寫入,而是在事務的執行過程中,便開始寫入到 redo buffer 中,最後以 redo log 文件方式 flush 到磁盤中。具體的落盤策略我們可以手動配置。

6.2 redo 如何實現事務持久性

  redo log 的出現,就是防止在發生故障的時間點,尚有髒頁未寫入磁盤,在重啓mysql服務的時候,根據redo log進行重做,從而達到事務的未入磁盤數據進行持久化這一特性。

6.3 redo 日誌如何保存

  引入 redo log 機制,InnoDB引擎認爲數據只要寫入到 redo log 成功,就認爲當前事務已經完成,而不是要求你將數據全部 flush 到磁盤(*.IBD 文件)纔算事務完成。如果每次修改都 flush 數據到磁盤,那顯然是很消耗時間的;但是如果你將日誌寫入到 redo log中,根據 redo log 落盤策略對數據進行保存,顯然效率會高很多(即:將耗時的操作放到後臺去操作)。
在這裏插入圖片描述
  如果此時 MySQL 服務異常、斷電等情況,在重啓時 MySQL 便會讀取 redo log 中的數據來進行恢復。此時你或許會有這樣一個疑惑:redo buffer 中的數據還沒有來得及寫入到 redo log 這部分的數據,不會丟嗎?此處就涉及到了 redo buffer 數據 落盤到 redo log 的策略問題。如下介紹

6.4 redo log 參數配置(不建議修改,推薦使用默認)

  Redo log 日誌,記錄在{datadir}/ib_logfile0、ib_logfile1 這兩個文件。我們可以進入 MySQL 所在數據目錄下查看。mysql 數據目錄在哪個位置,通過命令:show variables like 'datadir';查看。我們也可以通過命令:show variables like 'innodb_log_group_home_dir'來指定 redo log 存儲位置(不建議修改)
在這裏插入圖片描述
  一旦事務成功提交且數據持久化落盤之後,此時Redo log中的對應事務數據記錄就失去了意義,所以 Redo log 日誌文件的寫入採用的是循環寫入的方式。

我們也可以根據情況對 redo log 相關參數做修改,但是還是推薦你使用默認配置就好:

  1. 指定Redo log 日誌文件組中的數量 innodb_log_files_in_group,默認爲2。 (即:ib_logfile0 和 ib_logfile1)
  2. 指定Redo log 每一個日誌文件最大存儲量 innodb_log_file_size,默認48M。 (上圖中:50331648 就是 48M)
  3. 指定Redo log 在 cache/buffer 中的 buffer 池大小 innodb_log_buffer_size ,默認16M。 (即上圖中藍色 cache/buffer 區域大小)

6.5 數據落磁盤策略

  Redo buffer 持久化 Redo log 的策略,我們可以使用命令:show variables like 'Innodb_flush_log_at_trx_commit';查看。如下圖所示:默認爲1。
在這裏插入圖片描述
共有 3 個值可選:

  1. 取值 0。即:每一秒提交 redo bufferredo log os cacheflush cache to disk(如果突然斷電,最多丟失一秒的事務數據);
  2. 取值 1。(默認值)即:每次事務提交執行 redo bufferredo log os cacheflush cache to disk(最安全,但是性能最差);
  3. 取值 2。每次事務提交執行 redo bufferredo log os cache,再每一秒執行 flush cache to disk操作(此處分兩種情況:如果 MySQL 異常,此時已經保存到 redo log os cache,所以數據也是不會丟失的;每一秒執行 flush cache to disk,此時如果操作系統崩了,最多損失也是一秒的事務數據)

  在這三個策略中,MySQL 作爲一個 RDBMS,安全性一定是它優先考慮的,所以默認選擇的落盤策略爲 1。如果你要手工修改,在 0 和 2 之間,推薦使用 2。(此處也不推薦你來修改它,畢竟數據安全第一)


至此,InnoDB 引擎中的 MVCC 、Undo、Redo 機制就此介紹完畢。


MySQL 同系列文章,請參考:

  1. 瞭解MySQL體系結構
  2. 一文帶你看懂 MySQL 存儲引擎
  3. 還不瞭解 MyISAM 和 InnoDB 的區別?看這裏就夠了
  4. MySQL爲什麼沒有走索引?是這些原因在搞鬼
  5. 一條SQL語句的坎坷之旅(MySQL底層執行流程分析)
  6. 不會MySQL調優?來來瞅瞅SQL的執行計劃吧
  7. InnoDB 事務與鎖的前世今生
  8. 一文帶你瞭解 InnoDB 中的 MVCC、Undo、Redo 機制

博主寫作不易,加個關注唄

求關注、求點贊,加個關注不迷路 ヾ(◍°∇°◍)ノ゙

博主不能保證寫的所有知識點都正確,但是能保證純手敲,錯誤也請指出,望輕噴 Thanks♪(・ω・)ノ

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