淺析MySQL事務中的redo與undo

咱們都知道事務有4種特性:原子性、一致性、隔離性和持久性,在事務中的操做,要麼所有執行,要麼所有不作,這就是事務的目的。事務的隔離性由鎖機制實現,原子性、一致性和持久性由事務的redo 日誌和undo 日誌來保證。因此本篇文章將討論關於事務中的redo和undo的幾個問題:mysql

  • redo 日誌與undo日誌分別是什麼?
  • redo 如何保證事務的持久性?
  • undo log 是不是redo log的逆過程?

redo log

Redo 的類型

重作日誌(redo log)用來保證事務的持久性,即事務ACID中的D。實際上它能夠分爲如下兩種類型:sql

  • 物理Redo日誌
  • 邏輯Redo日誌

在InnoDB存儲引擎中,大部分狀況下 Redo是物理日誌,記錄的是數據頁的物理變化。而邏輯Redo日誌,不是記錄頁面的實際修改,而是記錄修改頁面的一類操做,好比新建數據頁時,須要記錄邏輯日誌。關於邏輯Redo日誌涉及更加底層的內容,這裏咱們只須要記住絕大數狀況下,Redo是物理日誌便可,DML對頁的修改操做,均須要記錄Redo.數據庫

Redo 的做用

Redo log的主要做用是用於數據庫的崩潰恢復緩存

Redo 的組成

Redo log能夠簡單分爲如下兩個部分:安全

  • 一是內存中重作日誌緩衝 (redo log buffer),是易失的,在內存中
  • 二是重作日誌文件 (redo log file),是持久的,保存在磁盤中

何時寫Redo?

寫入Redo的時機:bash

  • 在數據頁修改完成以後,在髒頁刷出磁盤以前,寫入redo日誌。注意的是先修改數據,後寫日誌
  • redo日誌比數據頁先寫回磁盤
  • 彙集索引、二級索引、undo頁面的修改,均須要記錄Redo日誌。

Redo的總體流程

下面以一個更新事務爲例,宏觀上把握redo log 流轉過程,以下圖所示:併發

mysql_redo

  • 第一步:先將原始數據從磁盤中讀入內存中來,修改數據的內存拷貝
  • 第二步:生成一條重作日誌並寫入redo log buffer,記錄的是數據被修改後的值
  • 第三步:當事務commit時,將redo log buffer中的內容刷新到 redo log file,對 redo log file採用追加寫的方式
  • 第四步:按期將內存中修改的數據刷新到磁盤中

redo如何保證 事務的持久性?

InnoDB是事務的存儲引擎,其經過Force Log at Commit 機制實現事務的持久性,即當事務提交時,先將 redo log buffer 寫入到 redo log file 進行持久化,待事務的commit操做完成時纔算完成。這種作法也被稱爲 Write-Ahead Log(預先日誌持久化),在持久化一個數據頁以前,先將內存中相應的日誌頁持久化。app

爲了保證每第二天志都寫入redo log file,在每次將redo buffer寫入redo log file以後,默認狀況下,InnoDB存儲引擎都須要調用一次 fsync操做,由於重作日誌打開並無 O_DIRECT選項,因此重作日誌先寫入到文件系統緩存。爲了確保重作日誌寫入到磁盤,必須進行一次 fsync操做。fsync是一種系統調用操做,其fsync的效率取決於磁盤的性能,所以磁盤的性能也影響了事務提交的性能,也就是數據庫的性能。 (O_DIRECT選項是在Linux系統中的選項,使用該選項後,對文件進行直接IO操做,不通過文件系統緩存,直接寫入磁盤)異步

上面提到的Force Log at Commit機制就是靠InnoDB存儲引擎提供的參數 innodb_flush_log_at_trx_commit來控制的,該參數能夠控制 redo log刷新到磁盤的策略,設置該參數值也能夠容許用戶設置非持久性的狀況發生,具體以下:函數

  • 當設置參數爲1時,(默認爲1),表示事務提交時必須調用一次 fsync 操做,最安全的配置,保障持久性
  • 當設置參數爲2時,則在事務提交時只作 write 操做,只保證將redo log buffer寫到系統的頁面緩存中,不進行fsync操做,所以若是MySQL數據庫宕機時 不會丟失事務,但操做系統宕機則可能丟失事務
  • 當設置參數爲0時,表示事務提交時不進行寫入redo log操做,這個操做僅在master thread 中完成,而在master thread中每1秒進行一次重作日誌的fsync操做,所以實例 crash 最多丟失1秒鐘內的事務。(master thread是負責將緩衝池中的數據異步刷新到磁盤,保證數據的一致性)

fsyncwrite操做其實是系統調用函數,在不少持久化場景都有使用到,好比 Redis 的AOF持久化中也使用到兩個函數。fsync操做 將數據提交到硬盤中,強制硬盤同步,將一直阻塞到寫入硬盤完成後返回,大量進行fsync操做就有性能瓶頸,而write操做將數據寫到系統的頁面緩存後當即返回,後面依靠系統的調度機制將緩存數據刷到磁盤中去,其順序是user buffer——> page cache——>disk。

usebuffer_pagecache_disk

Redo在InnoDB中是如何實現的?與mini-transaction的聯繫?

Redo的實現實則跟mini-transaction緊密相關,mini-transaction是一種InnoDB內部使用的機制,經過mini-transaction來保證併發事務操做下以及數據庫異常時數據頁中數據的一致性,但它不屬於事務。

爲了使得mini-transaction保證數據頁數據的一致性,mini-transaction必須遵循如下三種協議

  • The FIX Rules
  • Write-Ahead Log
  • Force-log-at-commit

The FIX Rules

修改一個數據頁時須要得到該頁的x-latch(排他鎖),獲取一個數據頁時須要該頁的s-latch(讀鎖或者稱爲共享鎖) 或者是 x-latch,持有該頁的鎖直到修改或訪問該頁的操做完成。

Write-Ahead Log

在前面闡述中就提到了Write-Ahead Log(預先寫日誌)。在持久化一個數據頁以前,必須先將內存中相應的日誌頁持久化。每一個頁都有一個LSN(log sequence number),表明日誌序列號,(LSN佔用8字節,單調遞增), 當一個數據頁須要寫入到持久化設備以前,要求內存中小於該頁LSN的日誌先寫入持久化設備。寫日誌採用append方式順序寫,是一種串行的方式,比起隨機寫,順序寫更能充分利用磁盤的性能。

Force-log-at-commit

這一點也就是前文提到的如何保證事務的持久性的內容,這裏再次總結一下,與上面的內容相呼應。在一個事務中能夠修改多個頁,Write-Ahead Log 能夠保證單個數據頁的一致性,可是沒法保證事務的持久性,Force-log-at-commit 要求當一個事務提交時,其產生全部的mini-transaction 日誌必須刷新到磁盤中,若日誌刷新完成後,在緩衝池中的頁刷新到持久化存儲設備前數據庫發生了宕機,那麼數據庫重啓時,能夠經過日誌來保證數據的完整性。

重作日誌的寫入流程

重作日誌寫入流程

上圖表示了重作日誌的寫入流程,每一個mini-transaction對應每一條DML操做,好比一條update語句,其由一個mini-transaction來保證,對數據修改後,產生redo1,首先將其寫入mini-transaction私有的Buffer中,update語句結束後,將redo1從私有Buffer拷貝到公有的Log Buffer中。當整個外部事務提交時,將redo log buffer再刷入到redo log file中。

undo log

undo log的定義

undo log主要記錄的是數據的邏輯變化,爲了在發生錯誤時回滾以前的操做,須要將以前的操做都記錄下來,而後在發生錯誤時才能夠回滾。

undo log的做用

undo是一種邏輯日誌,有兩個做用:

  • 用於事務的回滾
  • MVCC

關於MVCC(多版本併發控制)的內容這裏就很少說了,本文重點關注undo log用於事務的回滾。

undo日誌,只將數據庫邏輯地恢復到原來的樣子,在回滾的時候,它其實是作的相反的工做,好比一條INSERT ,對應一條 DELETE,對於每一個UPDATE,對應一條相反的 UPDATE,將修改前的行放回去。undo日誌用於事務的回滾操做進而保障了事務的原子性。

undo log的寫入時機

  • DML操做修改聚簇索引前,記錄undo日誌
  • 二級索引記錄的修改,不記錄undo日誌

須要注意的是,undo頁面的修改,一樣須要記錄redo日誌。

undo的存儲位置

在InnoDB存儲引擎中,undo存儲在回滾段(Rollback Segment)中,每一個回滾段記錄了1024個undo log segment,而在每一個undo log segment段中進行undo 頁的申請,在5.6之前,Rollback Segment是在共享表空間裏的,5.6.3以後,可經過 innodb_undo_tablespace設置undo存儲的位置。

undo的類型

在InnoDB存儲引擎中,undo log分爲:

  • insert undo log
  • update undo log

insert undo log是指在insert 操做中產生的undo log,由於insert操做的記錄,只對事務自己可見,對其餘事務不可見。故該undo log能夠在事務提交後直接刪除,不須要進行purge操做。

而update undo log記錄的是對delete 和update操做產生的undo log,該undo log可能須要提供MVCC機制,所以不能再事務提交時就進行刪除。提交時放入undo log鏈表,等待purge線程進行最後的刪除。

補充:purge線程兩個主要做用是:清理undo頁和清除page裏面帶有Delete_Bit標識的數據行。在InnoDB中,事務中的Delete操做實際上並非真正的刪除掉數據行,而是一種Delete Mark操做,在記錄上標識Delete_Bit,而不刪除記錄。是一種"假刪除",只是作了個標記,真正的刪除工做須要後臺purge線程去完成。

undo log 是不是redo log的逆過程?

undo log 是不是redo log的逆過程?其實從前文就能夠得出答案了,undo log是邏輯日誌,對事務回滾時,只是將數據庫邏輯地恢復到原來的樣子,而redo log是物理日誌,記錄的是數據頁的物理變化,顯然undo log不是redo log的逆過程。

redo & undo總結

下面是redo log + undo log的簡化過程,便於理解兩種日誌的過程:

假設有A、B兩個數據,值分別爲1,2.
1. 事務開始
2. 記錄A=1到undo log
3. 修改A=3
4. 記錄A=3到 redo log
5. 記錄B=2到 undo log
6. 修改B=4
7. 記錄B=4到redo log
8. 將redo log寫入磁盤
9. 事務提交
複製代碼

實際上,在insert/update/delete操做中,redo和undo分別記錄的內容都不同,量也不同。在InnoDB內存中,通常的順序以下:

  • 寫undo的redo
  • 寫undo
  • 修改數據頁
  • 寫Redo

小結

本文分析了事務中的redo和undo日誌,參考了一些資料書籍整理得出,可能有些地方表述的不清楚。若有不對之處,歡迎指出。

參考資料 & 鳴謝

相關文章
相關標籤/搜索