MySQL事務中的redo與undo
一 前言
眾所周知InnoDB 是一個事務性的存儲引擎,在上一小節我們提到事務有4種特性:原子性、一致性、隔離性和持久性,在事務中的操作,要么全部執行,要么全部不做,這就是事務的目的。
那么事務的四種特性到底是基於什么機制實現呢???
-
1、事務的原子性、隔離性由鎖機制實現,我們將在后續章節《數據庫鎖機制》中介紹
-
2、而事務的一致性和持久性由事務的 redo 日志和undo 日志來保證。
redo log 是重做日志,提供再寫入操作,實現事務的持久性; undo log 是回滾日志,提供回滾操作,保證事務的一致性。
本文將討論關於事務中的redo和undo的幾個問題:
- redo 日志與undo日志分別用於記錄什么?
- redo 如何保證事務的持久性?
- undo 如何保證事務的一致性?
- undo log 是否是redo log的逆過程?
二 Redo log
2.1 redo的作用
記錄的是尚未完成的操作,數據庫崩潰則用其重做
2.2 redo的組成
Redo log可以簡單分為以下兩個部分:
- 保存在內存中重做日志的緩沖 (redo log buffer),是易失的
- 保存在硬盤中重做日志文件 (redo log file),是持久的
2.3 redo工作流程
InnoDB 的更新操作采用的是 Write Ahead Log (預先日志持久化)策略,即先寫日志,再寫入磁盤。
當一條記錄更新時,redo流程大致如下
在內存更新數據后,會把更新后的記錄寫入到 redo log buffer 中。
第一步:InnoDB 會先把記錄從硬盤讀入內存
第二部:修改數據的內存拷貝
第三步:生成一條重做日志並寫入redo log buffer,記錄的是數據被修改后的值
第四步:當事務commit時,將redo log buffer中的內容刷新到 redo log file,對 redo log file采用追加寫的方式
第五步:定期將內存中修改的數據刷新到磁盤中(注意注意注意,不是從redo log file刷入磁盤,而是從內存刷入磁盤,redo log file只在崩潰恢復數據時才用),如果數據庫崩潰,則依據redo log buffer、redo log file進行重做,恢復數據,這才是redo log file的價值所在
2.4 Write Ahead Log
redo是如何保證事務的持久性的呢???
答案是Force Log at Commit 機制,即當事務commit提交時,innodb引擎先將 redo log buffer 寫入到 redo log file 進行持久化,待事務的commit操作完成時才算完成。這種做法也被稱為 Write-Ahead Log(預先日志持久化),在持久化一個數據頁之前,先將內存中相應的日志頁持久化。
問題1:為何不直接將修改的數據寫入磁盤,而是要write ahead log呢?
答案:用於崩潰恢復
詳解:
undo日志是對原始數據的備份
redo日志是對原始數據的修改
原始數據的按照既定的數據結構存放在磁盤上,寫入磁盤是要耗費巨大成本的,而寫入redo相對容易一些,因為redo里畢竟只需要考慮存放改動的數據即可,所以內存數據寫寫入redo log file,然后內存數據才能寫入磁盤,如此,在內存數據再寫入磁盤時因為某種原因比如斷電崩潰,那么還可以依據redo log file恢復數據,如下圖所示。
問題2:如何保證每次修改的數據都能寫入redo log file呢?
# 儲備知識1
O_DIRECT選項是在Linux系統中的選項,使用該選項后,對文件進行直接IO操作,不經過文件系統緩存,直接寫入磁盤
# 儲備知識2
redo log又稱之為重做日志,因重做日志打開並沒有O_DIRECT選項,所以重做日志先寫入到文件系統緩存,然后才會刷入硬盤,即
Redo log buffer--->os cache(文件系統緩存)--->redo log file
如果在刷入redo log file前斷電,則會丟失文件系統緩存中數據,數據未寫入redo log file,
因為由內存寫入redo log file在前,而由內存寫入磁盤在后,所以redo log file寫入失敗,則數據丟失
# 那如何保證每次的修改都記入日志文件redo log file呢???
答案是fsync操作
在每次將redo buffer寫入os cache文件系統緩存后,InnoDB存儲引擎都需要調用一次 fsync操作,保證立即由os cache文件系統緩存寫入redo log file
fsync是一種系統調用操作,其fsync的效率取決於磁盤的性能,因此磁盤的性能也影響了事務提交的性能,也就是數據庫的性能。
問題3:臟頁何時刷入磁盤呢?
# 儲備知識:臟頁
Buffer Pool 中更新的數據未刷新到磁盤中,該內存頁我們稱之為臟頁。最終臟頁的數據會刷新到磁盤中,將磁盤中的數據覆蓋,這個過程與 redo log 不一定有關系。
# 答案
redo log 日志滿了的情況下,會主動觸發臟頁刷新到磁盤
問題4:臟頁只在redo log滿的情況下才會刷入磁盤嗎?
答案:no,以下幾種情況同樣會觸發臟頁的刷新
- 1、系統內存不足時,需要將一部分數據頁淘汰掉,如果淘汰的是臟頁,需要先將臟頁同步到磁盤;
- 2、MySQL 認為空閑的時間,這種情況沒有性能問題;
- 3、MySQL 正常關閉之前,會把所有的臟頁刷入到磁盤,這種情況也沒有性能問題。
問題5:臟頁刷入會帶來性能問題嗎?
[rml_read_more]:
在生產環境中,如果我們開啟了慢 SQL 監控,你會發現偶爾會出現一些用時稍長的 SQL。**這是因為臟頁在刷新到磁盤時可能會給數據庫帶來性能開銷,**導致數據庫操作抖動。
2.5 參數innodb_flush_log_at_trx_commit
上面提到的Force Log at Commit機制就是靠InnoDB存儲引擎提供的參數innodb_flush_log_at_trx_commit
來控制的
該參數控制 commit提交事務 時,如何將 redo log buffer 中的日志刷新到 redo log file 中。
-
1、當設置參數為1時,(默認為1,建議),表示事務提交時必須調用一次
fsync
操作,最安全的配置,保障持久性 -
2、當設置參數為2時,則在事務提交時只做 write 操作,只保證將redo log buffer寫到系統的頁面緩存中,不進行fsync操作,因此如果MySQL數據庫宕機時 不會丟失事務,但操作系統宕機則可能丟失事務
-
3、當設置參數為0時,表示事務提交時不進行寫入redo log操作,這個操作僅在master thread 中完成,而在master thread中每1秒進行一次重做日志的fsync操作,因此實例 crash 最多丟失1秒鍾內的事務。(master thread是負責將緩沖池中的數據異步刷新到磁盤,保證數據的一致性)
拓展閱讀
我們需要注意的是 InnoDB 的 redo log 的大小是固定的,分別有多個日志文件采用循環方式組成一個循環閉環,當寫到結尾時,會回到開頭循環寫日志。我們可以通過參數 innodb_log_files_in_group 和 innodb_log_file_size 配置日志文件數量和每個日志文件的大小。
三 Undo log
3.1 作用
undo即撤銷還原。
用於記錄更改前的一份copy,在操作出錯時,可以用於回滾、撤銷還原,只將數據庫邏輯地恢復到原來的樣子
undo日志記錄了什么?
比如有兩個用戶訪問數據庫,當然並發羅。A是更改,B是查詢。
--A更改還沒有提交,B查詢的話,數據肯定為歷史數據,這個歷史數據就是來源於UNDO段,
--A更改未提交,需要回滾rollback,回滾rollback的數據也來至於UNDO段。
結論:為了並發時讀一致性成功,那么DML操作,肯定先寫UNDO段。
3.2 undo的存儲位置
在InnoDB存儲引擎中,undo存儲在回滾段(Rollback Segment)中,每個回滾段記錄了1024個undo log segment,而在每個undo log segment段中進行undo 頁的申請,在5.6以前,Rollback Segment是在共享表空間里的,5.6.3之后,可通過 innodb_undo_tablespace設置undo存儲的位置。
3.3 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線程去完成。
3.4 undo log 是否是redo log的逆過程?
undo log 是否是redo log的逆過程?其實從前文就可以得出答案了,undo log是邏輯日志,對事務回滾時,只是將數據庫邏輯地恢復到原來的樣子,而redo log是物理日志,記錄的是數據頁的物理變化,顯然undo log不是redo log的逆過程。
3.5 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
因為,數據在沒有commit前,是隨時從內存中寫入到表數據塊的,屬於臟數據。 數據庫崩潰后即使使用redo流程進行redo操作,但是臟數據還在,臟數據怎么處理,就只能靠undo流程,使用undo數據塊的舊數據覆蓋了。
undo與redo的聯系:但是不管是臟的還是舊的,都在redo日志中復制了一份。
- 1.undo是一種“數據文件datafile”,具有表空間,當然具有塊block;
- 2.redo是一種“文件file”,沒有表空間。
- 3.數據庫在DML事務時,先創建undo
- 4.讀一致性與一致性(scn相同)的區別
- 5.undo與rollback的區別:在undo(撤銷還原流程)中會使用rollback(回滾)這個動作