什么是redo log
? ? ? ? 如果我們只在內存的 Bufer Pool中修改了頁面,假設在事務提交后突然發生了某個故障導致內存中的數據都失效了,那么這個已經提交的事務在數據庫中所做的更改也就跟著丟失了,這是我們所不能忍受的。那么,如何保證這個持久性呢?一個很簡單的做法就是在事務提交完成之前,把該事務修改的所有頁面都刷新到磁盤。不過這個簡單粗暴的做法存在下面這些問題。
? ? ? ? 刷新一個完整的數據頁太浪費了。有時我們僅僅修改了某個頁面中的一個字節,但是由于InnoDB 是以頁為單位來進行磁盤I/O的,也就是說在該事務提交時不得不將一個完整的頁面從內存中刷新到磁盤。我們又知道,一個頁面的默認大小是16KB,因為修改了一個字節就要刷新 16KB的數據到磁盤上,顯然太浪費了。隨機 I/O 刷新起來比較慢。
? ? ? ? 一個事務可能包含很多語句,即使是一條語句也可能修改許多頁面,“倒霉催”的是該事務修改的這些頁面可能并不相鄰。這就意味著在將某個事務修改的 Bufer Pool中的頁面刷新到磁盤時,需要進行很多的隨機 I/O。隨機 I/O 比順序 I/O 要慢,尤其是對于傳統的機械硬盤。
? ? ? ? ?所以,其實沒有必在每次提交事務時就把該事務在內存中修改過的頁面全部刷新到磁盤,只需要記錄有哪些就該就可以。
????????這樣在事務提交時,就會把上述內容刷新到磁盤中。即使之后系統崩潰了,重啟之后只要照上述內容所記錄的步驟重新更新一下數據頁,那么該事務對數庫中所體的修改可以恢復出來,這樣也就意味著滿足持久性的要求。
? ? ? ? 我們把這類日志叫做redo log日志。
????????redo 日志占用的空間非常小,在存儲表空間ID、頁號、偏移量以及需要更新的值時需要的存儲空間很小。
redo 日志是順序寫入磁盤的,在執行事務的過程中,每執行一條語句,就可能產生若干條 redo 日志,這些日志是按照產生的順序寫入磁盤的,也就是使用順序 I/O。
redolog日志格式
redo 日志中各個部分的詳細解釋如下:
type :這條 redo 日志的類型。
page number:頁號。
space ID:表空間 ID。
data :這條 redo 日志的具體內容。
簡單的redolog日志
????????如果沒有為某個表顯式地定義主鍵,并且表中也沒有定義不允許存儲 NULL值的 UNIQUE鍵,那么InnoDB 會自動為表添加一個名為row_id的隱藏列作為主鍵。為這個row id 隱藏列進行賦值的方式如下。服務器會在內存中維護一個全局變量,每當向某個包含row_id隱藏列的表中插入一條記錄時,就會把這個全局變量的值當作新記錄的row_id列的值,并且把這個全局變量自增1。
每當這個全局變量的值為256的倍數時,就會將該變量的值刷新到系統表空間頁號為7的頁面中一個名為MaxRowID的屬性中(前文介紹表空間結構時詳細說過該屬性。之所以不是每次自增該全局變量時就將該值刷新到磁盤,是為了避免頻刷盤)
當系統啟動時,會將這個MaxRowD屬性加載到內存中,并將該值加上 256之后賦
值給前面提到的全局變量(因為在系統上次關機時,該全局變量的值可能大于磁盤頁面中 Max Row ID 屬性的值)。
這個 Max RowID 屬性占用的存儲空間是8字節。當某個事務向某個包含 row_id隱藏列的表插入一條記錄,并且為該記錄分配的row_id值為256的倍數時,就會向系統表空間頁號頭7的頁面的相應偏移量處寫入8字節的值。但是我們要知道,這個寫入操作實際上是在Bufe Pool中完成的,我們需要把這次對這個頁面的修改以redolog日志的形式記錄下來。這樣在事務提交之后,即使系統崩潰了,也可以將該頁面恢復成崩潰前的狀態。在這種對頁畫的修改是極其簡單的情況下,redo 日志中只需要記錄一下在某個頁面的某個偏移量處修改了幾個字節的值、具體修改后的內容是啥就好了。這種極其簡單的redo 日志稱為物理日志,并且根據在頁面中寫入數據的多少劃分了幾種不同的redo 日志類型。
復雜一些的 redo 日志類型
????????有時,在執行一條語句時會修改非常多的頁面,包括系統數據頁面和用戶數據頁面(用戶數據指的就是聚簇索引和二級索引對應的 B+樹)。以一條INSERT 語句為例,它除了向 B+樹的頁面中插入數據外,也可能更新系統數據MaxRowID的值。不過對于用戶來說,平時更關心的是語句對B+樹所做的更新。
????????表中包含多少個索引,一條INSERT 語句就可能更新多少棵 B+樹。針對某一棵B+樹來說,既可能更新葉子節點頁面,也可能更新內節點頁面,還可能創建新的頁面(在該記錄插入的葉子節點的剩余空間比較少,不足以存放該記錄時,會進行頁面分裂,在內節點頁面中添加目錄項記錄)。
????????在語句執行過程中,INSERT語句對所有頁面的修改都得保存到redo log日志中去。這句話說的比較輕巧,做起來可就比較麻煩了。比如,在將記錄插入到聚簇索引中時,如果定位到的葉子節點的剩余空間足夠存儲該記錄,那么只更新該葉子節點頁面,并只記錄一條 MLOGWRITE STRING類型的redo日志,表明在頁面的某個偏移量處增加了哪些數據不就好了么?那就天真了。別忘了,一個數據頁中除了存儲實際的記錄之外,還有FileHeader、PageHeader、Page Directory等部分(在第5章有詳細講解)。所以每往葉子節點代表的數據頁中插入一條記錄,還有其他很多地方會跟著更新,比如:
可能更新 Page Directory 中的槽信息;
可能更新 Page Header 中的各種頁面統計信息,比如 PAGE_N_DIR_SLOTS 表示的槽數量可能會更改,PAGEHEAPTOP代表的還未使用的空間最小地址可能會更改,PAGENHEAP代表的本頁面中的記錄數量可能會更改……
????????說了這么多,就是想表達:在把一條記錄插入到一個頁面時,需要更改的地方非常多。這時如果使用前面介紹的簡單的物理redo 日志來記錄這些修改,可以有兩種解決萬案。
????????方案1:在每個修改的地方都記錄一條redo日志。按照這種方式來記錄redo 日志的缺點是顯而易見的,因為被修改的地方實在太多了,可能redo 日志占用的空間都要比整個頁面占用的空間多。
方案 2:將整個頁面第一個被修改的字節到最后一個被修改的字節之間所有的數據當成一條物理 redo 日志中的具體數據。
正是因為在使用上面這兩個方案來記錄某個頁面中做了哪些修改時,比較浪費空間,設計ImnoDB 的大叔本著勤儉節約的初心,提出了一些新的redo 日志類型。
MLOG REC INSERT(type 字段對應的十進制數字為9):表示在插入一條使用非緊湊行格式(REDUNDANT)的記錄時,redo日志的類型,MLOG COMP RECINSERT(type 字段對應的十進制數字為38):表示在插入一條使用緊湊行格式(COMPACT、DYNAMIC、COMPRESSED)的記錄時,redo 日志的類型。MLOG COMP PAGE CREATE(type 字段對應的十進制數字為 58):表示在創建一個存儲緊湊行格式記錄的頁面時,redo日志的類型。
MLOG_COMP REC DELETE(type 字段對應的十進制數字為 42):表示在刪除一條使用緊湊行格式記錄時,redo日志的類型。