天天看點

mysql redo undo 簡書_淺析MySQL事務中的redo與undo

[TOC]

我們都知道事務有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 undo 簡書_淺析MySQL事務中的redo與undo

image.png

第一步:先将原始資料從磁盤中讀入記憶體中來,修改資料的記憶體拷貝

第二步:生成一條重做日志并寫入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是負責将緩沖池中的資料異步重新整理到磁盤,保證資料的一緻性)

fsync和write操作實際上是系統調用函數,在很多持久化場景都有使用到,比如 Redis 的AOF持久化中也使用到兩個函數。fsync操作 将資料送出到硬碟中,強制硬碟同步,将一直阻塞到寫入硬碟完成後傳回,大量進行fsync操作就有性能瓶頸,而write操作将資料寫到系統的頁面緩存後立即傳回,後面依靠系統的排程機制将緩存資料刷到磁盤中去,其順序是user buffer——> page cache——>disk。

mysql redo undo 簡書_淺析MySQL事務中的redo與undo

image.png

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 日志必須重新整理到磁盤中,若日志重新整理完成後,在緩沖池中的頁重新整理到持久化儲存設備前資料庫發生了當機,那麼資料庫重新開機時,可以通過日志來保證資料的完整性。

重做日志的寫入流程

[圖檔上傳失敗...(image-61b450-1603934473647)]

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