標籤:
重做日誌用來實現事務的持久性,即ACID中的D,由兩部分組成:
一是記憶體中的重做日誌緩衝(redo log buffer) 易丟失
二是重做記錄檔(redo log file) 持久的
InnoDB是事務的儲存引擎,其通過Force Log at Commit 機制實現事務的持久性,即當事務提交commit時,必須先將事務的所有日誌寫入到重做記錄檔進行持久化,待事務COMMIT操作完成才算完成,這裡的日誌指重做日誌,在InnoDB儲存引擎中,由兩部分組成,即redo log 和undo Log. redo log用來保證事務的持久性,undo log 用來幫主交易回復及MVCC的功能。redo log 基本是順序寫的,在資料庫運行時不需要對redo log的檔案進行讀取操作。而undo log 是需要進行隨機讀寫的
為了確保每次日誌都能寫入記錄檔,在每次將重做日誌緩衝寫入重做記錄檔後,InnoDB儲存引擎都需要調用一次fsync操作,由於重做記錄檔開啟並沒有使用O_DIRECT選項,因此重做日誌緩衝先寫入檔案系統快取。為了確保重做日誌寫入磁碟,必須進行fsync操作。由於fsync的效率取決於磁碟的效能,因此磁碟的效能決定了事務提交的效能,也就是資料庫的效能
InnoDB儲存引擎允許使用者手工設定非持久性的情況發生,以此來提高資料庫的效能。即當事務提交時,日誌不寫入重做日誌,而是等待一個時間周期後在執行fsync操作。由於並非強制在事務提交時進行一次fsync操作,顯然這可以提高資料庫的效能,但是當資料庫發生宕機時,由於部分日誌未書安心到磁碟,因此會丟失最後一段的事務
參數innodb_flush_log_at_trx_commit用來控制重做日誌重新整理到磁碟的策略,改參數預設為1 ,表示事務提交時必須調用一次fsync操作,還可以設定成0 和2 ,0表示事務提交時不進行寫入重做日誌操作,這個操作僅在master thread中完成,而master thread中每一秒會進行一次重做記錄檔的fsync操作,2 表示事務提交時將重做日誌寫入重做記錄檔,但僅寫入檔案系統的緩衝中,不進行fsync操作。在這個設定下,當MySQL發生宕機而作業系統不發生宕機時,並不會導致事務的丟失,而當作業系統宕機時,重啟資料庫會丟失未從檔案系統快取重新整理到重做記錄檔的那部分事務
參考一個列子,比較innodb_flush_log_at_trx_commit對事務的影響。首先根據如下代碼建立表t1和預存程序p_load
CREATE TABLE test_load(a INT,b CHAR(80))ENGINE=INNODB;DELIMITER //CREATE PROCEDURE p_load(COUNT INT UNSIGNED)BEGINDECLARE s INT UNSIGNED DEFAULT 1;DECLARE c CHAR(80) DEFAULT REPEAT(‘a‘,80);WHILE s<= COUNT DOINSERT INTO test_load SELECT NULL,c;COMMIT;SET s = s+1;END WHILE;END;//DELIMITER ;
預存程序p_load的作用是將資料不斷的插入表test_load中,並且每插入一條就進行一次顯示的COMMIT操作,在預設的設定下,參數innodb_flush_log_at_trx_commit為1的情況下,InnoDB會將重做日誌緩衝寫入檔案,並且調用一次fsync操作,如果執行CALL p_load(500 000),則會向表中插入50W行的記錄,執行50W次的fsync操作,先看看預設情況下插入50W條記錄需要的時間
CALL p_load(500000);
虛擬機器,插入50W條記錄,開銷18分鐘,對於生產環境來說,時間肯定是不能接受的,而造成時間比較饞的原因是在於fsync所需要的時間,那麼將參數設定成
SET GLOBAL innodb_flush_log_at_trx_commit=0;
再執行
CALL p_load(500000); 總耗時40秒
可以看到參數innodb_flush_log_at_trx_commit設定為0後,插入50W行的 記錄縮短了接近17分鐘。造成這麼大現象的主要原因是 後者大大減少了fsync的次數,從而挺高了資料庫執行的效能,如下表顯示了innodb_flush_log_at_trx_commit的不同設定下,調用預存程序p_load插入50W行記錄的時間
雖然使用者可以設定參數innodb_flush_log_at_trx_commit為0或2來提高事務提交的效能,但是需要牢記,這種設定喪失了事務的ACID特性,而針對上述預存程序,為了提高事務的提交效能,應該在將50W行記錄插入表後進行一次的COMMIT操作,而不是沒插入一條記錄後進行一次COMMIT操作。這樣的好處是還可以使事務方法在復原時會滾到事務最開始的確定狀態
在MySQL資料庫中還有一種二進位日誌其用來進行POINT-IN-TIME(PIT)的恢複及主從複製(Replication)環境的建立,從表面上看和重做日誌非常相似,都是記錄了對於資料庫操作的日誌,然而,從本質上來看,兩者有非常大的不同
首先,重做日誌是InnoDB儲存引擎層產生,而二進位日誌是在MySQL資料庫的上層產生,並且二進位日誌不僅僅針對InnoDB儲存引擎,MySQL資料庫中任何儲存引擎對於資料庫的編個都會產生二進位日誌
其次,兩種日誌記錄的內容形式不一樣,MySQL資料庫上層的二進位日誌是一種邏輯日誌,其記錄的是對應的SQL語句,而InnoDB儲存引擎層面的重做日誌是物理格式日誌,其記錄的是每個頁的修改
此外,兩種日誌記錄寫入磁碟的時間點不同,,二進位日誌只在事務提交完成後進行一次寫入,而InnoDB儲存引擎的重做日誌在事務進行中不斷地被寫入,這表現為日誌並不是隨事務提交的順序進行寫入的
從圖看到,二進位日誌近在事務提交時記錄,並且對每一個事務,僅包含對應事務的一個日誌,而對於InnoDB儲存引擎的重做日誌,由於其記錄的是物理動作記錄,因此每個事務對應多個日誌條目,並且事務的重做日誌是並發的,並非在事務提交時寫入,故其在檔案中的記錄順序並非是事務的開始順序。*T1 * T2 *T3表示事務提交時的日誌
2 log block
在InnoDB儲存引擎中,重做日誌都是以512位元組進行儲存的,這意味著重做日誌緩衝、重做記錄檔塊都是以塊block的方式進行儲存的,稱為重做日誌塊(redo log block)每塊的大小512位元組
每個頁中產生的重做日誌數量大於512位元組,那麼需要分割多個重做日誌塊進行儲存,此外,由於重做日誌快的大小和磁碟扇區大小一樣,都是512位元組,因此重做日誌的寫入可以保證原子性,不需要double write技術
重做日誌快除了日誌本身之外,還由日誌塊頭(log block header)及日誌塊尾(log block tailer)兩部分組成。重做日誌頭一共佔用12位元組,重做日誌尾佔用8位元組。故每個重做日誌塊實際可以儲存的大小為492位元組(512-12-8),顯示重做日誌塊緩衝的結構
顯示了重做日誌緩衝的結果,可以發現,重做日誌緩衝由每個為512位元組大小的日誌塊鎖組成,日誌塊由三部分組成,依次為日誌快頭(log block header)、日誌內容(log body)、日誌塊尾(log block tailer)
log block header由4部分組成
log buffer 是由log block組成,在內部log buffer就好似一個數組,因此LOG_BLOCK_HDR_NO用來標記這個數組中的位置,尤其是遞增並且迴圈使用的。佔用4個位元組。但是由於第一位用來判斷是否是flush bit,所以最大值為2G
LOG_BLOCK_HDR_DATA_LEN佔用2個位元組,表示log block所佔用的大小,當log block被寫滿時,該值為0x200,表示使用全部的log block空間,即佔用512位元組
LOG_BLOCK_FIRST_REC_GROUP 佔用2個位元組,表示log block中第一個日誌所在的位移量。如果該值的大小和LOG_BLOCK_HDR_DATA_LEN相同,則表示當前log block不包含新的日誌。如事務T1的重做日誌1佔用762位元組,事務T2的重做日誌佔用100位元組,。由於每個log block實際只能儲存492位元組,因此其在log buffer的情況應該
從圖可以觀察到,由於事務T1的重做日誌佔用792位元組,因此需要佔用兩個log block。左側的log block中 LOG_BLOCK_FIRST_REC_GROUP為12,級log block中第一個日誌的開始位置,在第二個log block中,由於包含了之前事務T1的重做日誌,事務T2的日誌才是log block中第一日誌,因此該log block的LOG_BLOCK_FIRST_REC_GROUP為(270+12)
LOG_BLOCK_CHECKPOINT_NO佔用4位元組,表示該log block最後被寫入時的檢查點第4位元組的值
log block tailer 只由1個部分組成,且值和LOG_BLOCK_HDR_NO相同,並在函數log_block_init中被初始化 LOG_BLOCK_TRL_NO 大小為4位元組
3 log group
log group 重做日誌組,其中有多個重做記錄檔,雖然源碼已經支援log group的景象功能,但是在ha_innobase.cc檔案中禁止了該功能,因此,InnoDB儲存引擎實際只由一個log group
log group是一個邏輯的概念,並沒有一個實際的物理檔案來表示log group資訊,log group 由多個重做記錄檔組成,每個log group中的記錄檔是相同的,且在InnoDB 1.2版本之前,重做記錄檔的總大小要小於4GB,從InnoDB 1.2版本開始重做記錄檔的總大小限制提高為512GB,InnoSQL版本的InnoDB儲存引擎在1.1版本就支援大於4GB的重做日誌
重做記錄檔中儲存就是之前log buffer中儲存的log block。因此其也是根據塊的方式進行實體儲存體的管理,每個塊的大小與log block一樣,同樣為512位元組,在InnoDB儲存引擎運行過程中,log buffer根據一定的規則將記憶體中的log block重新整理到磁碟。這個規則具體是
事務提交時
當log buffer中有一半的記憶體空間已經被使用時
log checkpoint時
對於log block的寫入追加在redo log file最後部分,當一個redo log file寫滿時,會接著寫下一個redo log file,其使用的方式為round-robin
雖然log block總是在redo log file的最後部分進行寫入,有的讀者可能以為對redo log file的寫入時順序的,其實不是,因為redo log file除了儲存log buffer重新整理到磁碟的log block,還儲存了一些其他的資訊,這些資訊一共佔用2KB大小,即每個redo log file的前2KB的部分不儲存log block資訊,對於log group中的第一個redo log file,其前2KB的部分儲存4個512位元組大小的塊,其中存放的內容為
需要特別注意,上述資訊僅在每個log group的第一個redo log file中進行儲存,log group中的其餘redo log file僅保留這些空間,但不儲存上述資訊。正因為儲存了這些資訊,就意味著對redo log file 的寫入並不是完全順序的。因為其除了log block的寫入操作,還需要更新前2KB部分的資訊,這些資訊對於InnoDB儲存引擎的恢複操作來說非常關鍵和重要,故log group與redo log file 之間的關係如下
在log filer header 後面的部分為InnoDB儲存引擎儲存的checkpoint(檢查點)值,其設計時交替寫入。這樣的設計避免了因介質失敗而導致無法找到可用的checkpoint的情況
4 重做日誌格式
不同的資料庫操作會有對應的重做日誌格式。此外,由於InnoDB儲存引擎的儲存管理是基於頁的,故其重做日誌格式也是基於頁的。雖然有著不同的重做日誌格式,但他們有著通用的頭部格式,
通用的頭部格式由一下3部分組成
redo_log_type 重做日誌類型
space: 資料表空間ID
page_no 頁的位移量
之後是redo log body ,根據重做日誌類型的不對,會有不同的儲存內容,例如,對於頁上記錄的插入和刪除操作,分別對應的的格式
MySQL中redo日誌