淺析MySQL事務中的redo與undo

pjmike_pj發表於2019-01-14

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

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

redo log

Redo 的型別

重做日誌(redo log)用來保證事務的永續性,即事務ACID中的D。實際上它可以分為以下兩種型別:

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

在InnoDB儲存引擎中,大部分情況下 Redo是物理日誌,記錄的是資料頁的物理變化。而邏輯Redo日誌,不是記錄頁面的實際修改,而是記錄修改頁面的一類操作,比如新建資料頁時,需要記錄邏輯日誌。關於邏輯Redo日誌涉及更加底層的內容,這裡我們只需要記住絕大數情況下,Redo是物理日誌即可,DML對頁的修改操作,均需要記錄Redo.

Redo 的作用

Redo log的主要作用是用於資料庫的崩潰恢復

Redo 的組成

Redo log可以簡單分為以下兩個部分:

  • 一是記憶體中重做日誌緩衝 (redo log buffer),是易失的,在記憶體中
  • 二是重做日誌檔案 (redo log file),是持久的,儲存在磁碟中

什麼時候寫Redo?

寫入Redo的時機:

  • 在資料頁修改完成之後,在髒頁刷出磁碟之前,寫入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(預先日誌持久化),在持久化一個資料頁之前,先將記憶體中相應的日誌頁持久化。

為了保證每次日誌都寫入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日誌,參考了一些資料書籍整理得出,可能有些地方表述的不清楚。如有不對之處,歡迎指出。

參考資料 & 鳴謝

相關文章