圖文結(jié)合帶你搞定MySQL日志之Undo log(回滾日志)

什么是Undo Log?
Undo:意為撤銷或取消,以撤銷操作為目的,返回某個狀態(tài)的操作。
Undo Log:數(shù)據(jù)庫事務(wù)開始之前,會將要修改的記錄放到Undo日志里,當(dāng)事務(wù)回滾時或者數(shù)據(jù)庫崩潰時,可以利用UndoLog撤銷未提交事務(wù)對數(shù)據(jù)庫產(chǎn)生的影響。
Undo Log是事務(wù)原子性的保證。在事務(wù)中更新數(shù)據(jù)的前置操作其實是要先寫入一個Undo Log
如何理解Undo Log
事務(wù)需要保證原子性,也就是事務(wù)中的操作要么全部完成,要么什么也不做。但有時候事務(wù)執(zhí)行到一半會出現(xiàn)一些情況,比如:
- 情況一:事務(wù)執(zhí)行過程中可能遇到各種錯誤,比如服務(wù)器本身的錯誤,操作系統(tǒng)錯誤,甚至是突然斷電導(dǎo)致的錯誤。
- 情況二:DBA可以在事務(wù)執(zhí)行過程中手動輸入ROLLBACK語句結(jié)束當(dāng)前事務(wù)的執(zhí)行。以上情況出現(xiàn),我們需要把數(shù)據(jù)改回原先的樣子,這個過程稱之為回滾。
每當(dāng)我們要對一條記錄做改動時(這里的改動可以指INSERT、DELETE、UPDATE),都需要把回滾時所需的東西記下來。比如:
- 你插入一條記錄時,至少要把這條記錄的主鍵值記下來,之后回滾的時候只需要把這個主鍵值對應(yīng)的記錄刪掉就好了。(對于每個INSERT, InnoDB存儲引擎會完成一個DELETE)
- 你刪除了一條記錄,至少要把這條記錄中的內(nèi)容都記下來,這樣之后回滾時再把由這些內(nèi)容組成的記錄插入到表中就好了。(對于每個DELETE,InnoDB存儲引擎會執(zhí)行一個INSERT)
- 你修改了一條記錄,至少要把修改這條記錄前的舊值都記錄下來,這樣之后回滾時再把這條記錄更新為舊值就好了。(對于每個UPDATE,InnoDB存儲引擎會執(zhí)行一個相反的UPDATE,將修改前的行放回去)
MySQL把這些為了回滾而記錄的這些內(nèi)容稱之為撤銷日志?或者回滾日志(即Undo Log)。注意,由于查詢操作(SELECT)并不會修改任何用戶記錄,所以在杳詢操作行時,并不需要記錄相應(yīng)的Undo日志
此外,Undo Log會產(chǎn)生Redo Log,也就是Undo Log的產(chǎn)生會伴隨著Redo Log的產(chǎn)生,這是因為Undo Log也需要持久性的保護(hù)。
Undo Log的功能
- ?提供數(shù)據(jù)回滾-原子性當(dāng)事務(wù)回滾時或者數(shù)據(jù)庫崩潰時,可以利用Undo Log來進(jìn)行數(shù)據(jù)回滾。
- 多版本并發(fā)控制(MVCC)-隔離性即在InnoDB存儲引擎中MVCC的實現(xiàn)是通過Undo Log來完成。當(dāng)用戶讀取一行記錄時,若該記錄已經(jīng)被其他事務(wù)占用,當(dāng)前事務(wù)可以通過Undo Log讀取之前的行版本信息,以此實現(xiàn)非鎖定讀取。
Undo Log的存儲結(jié)構(gòu)
- 回滾段與Undo頁
InnoDB對Undo Log的管理采用段的方式,也就是回滾段(rollback segment)?。每個回滾段記錄了1024 個Undo Log segment?,而在每個Undo Log segment段中進(jìn)行Undo頁的申請。
在InnoDB1.1版本?之前(不包括1.1版本),只有一個rollback segment,因此支持同時在線的事務(wù)限制為 1024。雖然對絕大多數(shù)的應(yīng)用來說都已經(jīng)夠用。
從1.1版本開始InnoDB支持最大128個rollback segment?,故其支持同時在線的事務(wù)限制提高到了128*1024。
mysql> show variables like 'innodb_undo_logs';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_undo_logs | 128 |
+------------------+-------+
雖然InnoDB1.1版本支持了128個rollback segment?,但是這些rollback segment?都存儲于共享表空間ibdata中。從lnnoDB1.2版本開始,可通過參數(shù)對rollback segment做進(jìn)一步的設(shè)置。這些參數(shù)包括:
innodb_undo_directory:設(shè)置rollback segment文件所在的路徑。這意味著rollback segment可以存放在共享表空間以外的位置,即可以設(shè)置為獨立表空間。該參數(shù)的默認(rèn)值為“./”,表示當(dāng)前InnoDB存儲引擎的目錄。
innodb_undo_logs:設(shè)置rollback segment的個數(shù),默認(rèn)值為128。在InnoDB1.2版本中,該參數(shù)用來替換之前版本的參數(shù)innodb_rollback_segments。
innodb_undo_tablespaces:設(shè)置構(gòu)成rollback segment文件的數(shù)量,這樣rollback segment可以較為平均地分布在多個文件中。設(shè)置該參數(shù)后,會在路徑innodb_undo_directory看到undo為前綴的文件,該文件就代表rollback segment文件。
- 回滾段與事務(wù)
1.每個事務(wù)只會使用一個回滾段(rollback segment),一個回滾段在同一時刻可能會服務(wù)于多個事務(wù)。
2.當(dāng)一個事務(wù)開始的時候,會制定一個回滾段,在事務(wù)進(jìn)行的過程中,當(dāng)數(shù)據(jù)被修改時,原始的數(shù)據(jù)會被復(fù)制到回滾段。
3.在回滾段中,事務(wù)會不斷填充盤區(qū),直到事務(wù)結(jié)束或所有的空間被用完。如果當(dāng)前的盤區(qū)不夠用,事務(wù)會在段中請求擴(kuò)展下一個盤區(qū),如果所有已分配的盤區(qū)都被用完,事務(wù)會覆蓋最初的盤區(qū)或者在回滾段允許的情況下擴(kuò)展新的盤區(qū)來使用。
4.回滾段存在于Undo表空間中,在數(shù)據(jù)庫中可以存在多個Undo表空間,但同一時刻只能使用一個Undo表空間。
5.當(dāng)事務(wù)提交時,InnoDB存儲引擎會做以下兩件事情: 1.將Undo Log放入列表中,以供之后的purge(清洗、清除)操作 2.判斷Undo Log所在的頁是否可以重用(低于3/4可以重用),若可以分配給下個事務(wù)使用
- 回滾段中的數(shù)據(jù)分類
未提交的回滾數(shù)據(jù)(uncommitted undo information):該數(shù)據(jù)所關(guān)聯(lián)的事務(wù)并未提交,用于實現(xiàn)讀一致性,所以該數(shù)據(jù)不能被其他事務(wù)的數(shù)據(jù)覆蓋。
已經(jīng)提交但未過期的回滾數(shù)據(jù)(committed undo information):該數(shù)據(jù)關(guān)聯(lián)的事務(wù)已經(jīng)提交,但是仍受到undo retention參數(shù)的保持時間的影響。
事務(wù)已經(jīng)提交并過期的數(shù)據(jù)(expired undo information):事務(wù)已經(jīng)提交,而且數(shù)據(jù)保存時間已經(jīng)超過undo retention參數(shù)指定的時間,屬于已經(jīng)過期的數(shù)據(jù)。當(dāng)回滾段滿了之后,會優(yōu)先覆蓋"事務(wù)已經(jīng)提交并過期的數(shù)據(jù)"。
- Undo頁的重用
當(dāng)我們開啟一個事務(wù)需要寫Undo log的時候,就得先去Undo Log segment?中去找到一個空閑的位置,當(dāng)有空位的時候,就去申請Undo頁,在這個申請到的Undo頁中進(jìn)行Undo Log的寫入。我們知道MySQL默認(rèn)一頁的大小是16k。
為每一個事務(wù)分配一個頁,是非常浪費的(除非你的事務(wù)非常長),假設(shè)你的應(yīng)用的TPS(每秒處理的事務(wù)數(shù)目)為1000,那么1s就需要1000個頁,大概需要16M的存儲,1分鐘大概需要1G的存儲。如果照這樣下去除非MySQL清理的非常勤快,否則隨著時間的推移,磁盤空間會增長的非常快,而且很多空間都是浪費的。
于是Undo頁就被設(shè)計的可以重用了,當(dāng)事務(wù)提交時,并不會立刻刪除Undo頁。因為重用,所以這個Undo頁可能混雜著其他事務(wù)的Undo Log。Undo Log在commit后,會被放到一個鏈表中,然后判斷Undo頁的使用空間是否小于3/4,如果小于3/4的話,則表示當(dāng)前的Undo頁可以被重用,那么它就不會被回收,其他事務(wù)的Undo Log可以記錄在當(dāng)前Undo頁的后面。由于Undo Log是離散的,所以清理對應(yīng)的磁盤空間時,效率不高。
- Undo Log日志的存儲機(jī)制

如上圖,可以看到,Undo Log日志里面不僅存放著數(shù)據(jù)更新前的記錄,還記錄著RowID、事務(wù)ID、回滾指針。其中事務(wù)ID每次遞增,回滾指針第一次如果是INSERT語句的話,回滾指針為NULL,第二次UPDATE之后的Undo Log的回滾指針就會指向剛剛那一條Undo Log日志,以此類推,就會形成一條Undo Log的回滾鏈,方便找到該條記錄的歷史版本。
Undo Log的工作原理
在更新數(shù)據(jù)之前,MySQL會提前生成Undo Log日志,當(dāng)事務(wù)提交的時候,并不會立即刪除Undo Log,因為后面可能需要進(jìn)行回滾操作,要執(zhí)行回滾(ROLLBACK)操作時,從緩存中讀取數(shù)據(jù)。Undo Log日志的刪除是通過通過后臺purge線程進(jìn)行回收處理的。

- 事務(wù)A執(zhí)行UPDATE操作,此時事務(wù)還沒提交,會將數(shù)據(jù)進(jìn)行備份到對應(yīng)的Undo Buffer,然后由Undo Buffer持久化到磁盤中的Undo Log文件中,此時Undo Log保存了未提交之前的操作日志,接著將操作的數(shù)據(jù),也就是test表的數(shù)據(jù)持久保存到InnoDB的數(shù)據(jù)文件IBD。
- 此時事務(wù)B進(jìn)行查詢操作,直接從Undo Buffer緩存中進(jìn)行讀取,這時事務(wù)A還沒提交事務(wù),如果要回滾(ROLLBACK)事務(wù),是不讀磁盤的,先直接從Undo Buffer緩存讀取。
Undo Log的類型
在InnoDB存儲引擎中,Undo Log分為:
- insert Undo
Loginsert Undo Log是指在insert操作中產(chǎn)生的Undo Log。因為insert操作的記錄,只對事務(wù)本身可見,對其他事務(wù)不可見(這是事務(wù)隔離性的要求),故該Undo Log可以在事務(wù)提交后直接刪除。不需要進(jìn)行purge操作。
- update Undo
Logupdate Undo Log記錄的是對delete和update操作產(chǎn)生的Undo Log。該Undo Log可能需要提供MVCC機(jī)制,因此不能在事務(wù)提交時就進(jìn)行刪除。提交時放入Undo Log鏈表,等待purge線程進(jìn)行最后的刪除。
Undo Log的生命周期
簡要生成過程
以下是Undo+Redo事務(wù)的簡化過程:
假設(shè)有2個數(shù)值,分別為 A=1 和 B=2 ,然后將A修改為3,B修改為4
1. start transaction;
2.記錄A=1到Undo Log;
3. update A = 3;
4.記錄A=3 到Redo Log;
5.記錄B=2到Undo Log;
6. update B = 4;
7.記錄B=4到Redo Log;
8.將Redo Log刷新到磁盤;
9. commit
- 在1-8步驟的任意一步系統(tǒng)宕機(jī),事務(wù)未提交,該事務(wù)就不會對磁盤上的數(shù)據(jù)做任何影響。
- 如果在8-9之間宕機(jī)。
Redo Log 進(jìn)行恢復(fù)
Undo Log 發(fā)現(xiàn)有事務(wù)沒完成進(jìn)行回滾。
- 若在9之后系統(tǒng)宕機(jī),內(nèi)存映射中變更的數(shù)據(jù)還來不及刷回磁盤,那么系統(tǒng)恢復(fù)之后,可以根據(jù)Redo Log把數(shù)據(jù)刷回磁盤。
流程圖:

Undo Log的配置參數(shù)
- innodb_max_undo_log_size:Undo日志文件的最大值,默認(rèn)1GB,初始化大小10M
- innodb_undo_log_truncate:標(biāo)識是否開啟自動收縮Undo Log表空間的操作
- innodb_undo_tablespaces:設(shè)置獨立表空間的個數(shù),默認(rèn)為0,標(biāo)識不開啟獨立表空間,Undo日志保存在ibdata1中
- innodb_undo_directory:Undo日志存儲的目錄位置 innodb_undo_logs: 回滾的個數(shù) 默認(rèn)128
參考文章
《MySQL是怎樣運行的--從根兒上理解MySQL》—小孩子4919(https://juejin.cn/book/6844733769996304392)
































