mysql中 redo日志詳解
大家好。上篇文章我們介紹了什么是redo日志以及redo日志的寫入過程。建議沒看過上篇文章的同學(xué)先看一下上一篇文章,今天我們繼續(xù)來說一說redo日志。
一、redo日志文件
1. redo日志刷盤時機
我們知道m(xù)tr運行過程中產(chǎn)生的一組redo日志在mtr結(jié)束時會被復(fù)制到log buffer中,可是這些日志總在內(nèi)存里呆著也不是個辦法,在一些情況下它們會被刷新到磁盤里,比如:
- log buffer空間不足時:log buffer的大小是有限的(通過系統(tǒng)變量 innodb_log_buffer_size 指定),如果不停的往這個有限大小的log buffer 里塞入日志,很快它就會被填滿。InnoDB認(rèn)為如果當(dāng)前寫入log buffer 的redo日志量已經(jīng)占滿了log buffer總?cè)萘康拇蠹s一半左右,就需要把這些日志刷新到磁盤上。
- 事務(wù)提交時:我們前邊說過之所以使用redo日志主要是因為它占用的空間少,還是順序?qū)懀谑聞?wù)提交時可以不把修改過的Buffer Pool頁面刷新到磁盤,但是為了保證持久性,必須要把修改這些頁面對應(yīng)的redo日志刷新到磁盤。
- 后臺線程不停的刷刷刷:后臺有一個線程,大約每秒都會刷新一次log buffer中的redo日志到磁盤。
- 正常關(guān)閉服務(wù)器時。
- 做所謂的checkpoint時(一會兒介紹過checkpoint的概念)
- 其他的一些情況…
2. redo日志文件組
MySQL的數(shù)據(jù)目錄(使用 SHOW VARIABLES LIKE ‘datadir’ 查看)下默認(rèn)有兩個名為ib_logfile0和ib_logfile1的文件, log buffer中的日志默認(rèn)情況下就是刷新到這兩個磁盤文件中。如果我們對默認(rèn)的 redo 日志文件不滿意,可以通過下邊幾個啟動參數(shù)來調(diào)節(jié):
innodb_log_group_home_dir
該參數(shù)指定了redo 日志文件所在的目錄,默認(rèn)值就是當(dāng)前的數(shù)據(jù)目錄。
innodb_log_file_size
該參數(shù)指定了每個redo日志文件的大小。
innodb_log_files_in_group
該參數(shù)指定redo日志文件的個數(shù),默認(rèn)值為2,最大值為100。
從上邊的描述中可以看到,磁盤上的redo日志文件不只一個,而是以一個日志文件組的形式出現(xiàn)的。這些文件以ib_logfile[數(shù)字]的形式進(jìn)行命名。在將redo日志寫入日志文件組時,是 從ib_logfile0開始寫,如果 ib_logfile0 寫滿了,寫入ib_logfile1中,依此類推。如果寫到最后一個文件,那就重新轉(zhuǎn)到ib_logfile0繼續(xù)寫,所以整個過程如下圖所示:
總共的redo 日志文件大小其實就是:innodb_log_file_size × innodb_log_files_in_group。
3. redo日志文件格式
log buffer本質(zhì)上是一片連續(xù)的內(nèi)存空間,被劃分成了若干個512字節(jié)大小的block。將log buffer中的redo日志刷新到磁盤的本質(zhì)就是把block的鏡像寫入日志文件中,所以redo日志文件其實也是由若干個512 字節(jié)大小的block組成。
redo日志文件組中的每個文件大小都一樣,格式也一樣,都是由兩部分組成:
- 前2048個字節(jié),也就是前4個block是用來存儲一些管理信息的。
- 從第2048字節(jié)往后是用來存儲log buffer中的block鏡像的。
所以我們前邊所說的循環(huán)使用redo日志文件,其實是從每個日志文件的第2048個字節(jié)開始算,畫個示意圖就是這樣:
下面我們介紹一下每個redo日志文件前2048個字節(jié),也就是前4個特殊block的格式都是干嘛的,廢話少說,直接看圖:
從圖中可以看出來,這4個block分別是:
log file header: 描述該 redo 日志文件的一些整體屬性,看一下它的結(jié)構(gòu):
各個屬性的具體釋義如下:
屬性名 | 長度(字節(jié)) | 描述 |
---|---|---|
LOG_HEADER_FORMAT | 4 | redo 日志的版本 |
LOG_HEADER_PAD1 | 4 | 用于字節(jié)填充,沒有什么意義。 |
LOG_HEADER_START_LSN | 8 | 標(biāo)記本 redo 日志文件開始的LSN值,也就是文件偏移量為2048字節(jié)初處對應(yīng)的LSN值 |
LOG_HEADER_CREATOR | 32 | 一個字符串, 標(biāo)記本redo 日志文件的創(chuàng)建者是誰。正常運行時該值為MySQL 的版本號,比如:“MySQL 5.7.21” ,使用 mysqlbackup 命令創(chuàng)建的 redo 日志文件的該值為 “ibbackup” 和創(chuàng)建時間。 |
LOG_BLOCK_CHECKSUM | 4 | 本block的校驗值,所有block都有,我們不關(guān)心 |
checkpoint1: 記錄關(guān)于 checkpoint 的一些屬性,結(jié)構(gòu)如下所示:
屬性 | 長度(字節(jié)) | 描述 |
---|---|---|
LOG_CHECKPOINT_NO | 8 | 服務(wù)器做 checkpoint 的編號,每做 一次checkpoint ,該值就加1。 |
LOG_CHECKPOINT_LSN | 8 | 服務(wù)器做 checkpoint 結(jié)束時對應(yīng)的 LSN 值,系 統(tǒng)崩潰恢復(fù)時將從該值開始。 |
LOG_CHECKPOINT_OFFSET | 8 | 上個屬性中的 LSN 值在 redo 日志文件組中的 偏移量 |
LOG_CHECKPOINT_LOG_BUF_SIZE | 8 | 服務(wù)器在做 checkpoint 操作時對應(yīng)的 log buffer 的大小 |
LOG_BLOCK_CHECKSUM | 4 | 本block的校驗值,所有block都有,我們不關(guān)心 |
checkpoint1: 記錄關(guān)于 checkpoint 的一些屬性,結(jié)構(gòu)如下所示:
屬性 | 長度(字節(jié)) | 描述 |
---|---|---|
LOG_CHECKPOINT_NO | 8 | 服務(wù)器做 checkpoint 的編號,每做 一次checkpoint ,該值就加1。 |
LOG_CHECKPOINT_LSN | 8 | 服務(wù)器做 checkpoint 結(jié)束時對應(yīng)的 LSN 值,系 統(tǒng)崩潰恢復(fù)時將從該值開始。 |
LOG_CHECKPOINT_OFFSET | 8 | 上個屬性中的 LSN 值在 redo 日志文件組中的 偏移量 |
LOG_CHECKPOINT_LOG_BUF_SIZE | 8 | 服務(wù)器在做 checkpoint 操作時對應(yīng)的 log buffer 的大小 |
LOG_BLOCK_CHECKSUM | 4 | 本block的校驗值,所有block都有,我們不關(guān)心 |
第三個block未使用,忽略~
checkpoint2: 結(jié)構(gòu)和checkpoint1一樣。
二、Log Sequeue Number
自mysql開始運行,就會不斷的生成redo日志。所以redo日志的量在不斷的遞增,永遠(yuǎn)不會縮減。Inno為記錄已經(jīng)寫入的redo日志量,設(shè)計了一個稱之為Log Sequeue Number的全局變量–日志序列號,簡稱 lsn 。InnoDB規(guī)定初始的lsn值為8704。
我們知道在向log buffer中寫入redo日志時不是一條一條寫入的,而是以一個mtr生成的一組redo日志為單位進(jìn)行寫入的。而且實際上是把日志內(nèi)容寫在了log block body處。但是在統(tǒng)計lsn的增長量時,是按照實際 寫入的日志量加上占用的log block header和log block trailer來計算的。我們來看一個例子:
系統(tǒng)第一次啟動后初始化log buffer時, buf_free(就是標(biāo)記下一條 redo日志應(yīng)該寫入到log buffer的位置的變量)就會指向第一個block的偏移量為12字節(jié)(log block header 的大?。┑牡胤?,那么 lsn 值也會跟著增加12:
如果某個mtr產(chǎn)生的一組redo日志占用的存儲空間比較小,也就是待插入的block剩余空閑空間能容納這個mtr提交的日志時,lsn增長的量就是該mtr生成的redo日志占用的字節(jié)數(shù),假設(shè)mtr_1產(chǎn)生的redo日志量為200字節(jié),那么lsn就要在8716的基礎(chǔ)上增加200 ,變?yōu)?916 。就像這樣:
如果某個mtr產(chǎn)生的一組redo日志占用的存儲空間比較大,也就是待插入的block剩余空閑空間不足以容納這個mtr提交的日志時,lsn增長的量就是該mtr 生成的redo日志占用的字節(jié)數(shù)加上額外占用的log block header和log block trailer的字節(jié)數(shù),就像這樣:
所以:每一組由mtr生成的redo日志都有一個唯一的LSN值與其對應(yīng),LSN值越小,說明 redo日志產(chǎn)生的越早。
1. flushed_to_disk_lsn
redo日志是首先寫到log buffer中,之后才會被刷新到磁盤上的redo 日志文件。所以InnoDB提出了一個稱之為buf_next_to_write的全局變量,標(biāo)記當(dāng)前l(fā)og buffer中已經(jīng)有哪些日志被刷新到磁盤中了。畫個圖表示就是這樣:
我們前邊說lsn 是表示當(dāng)前系統(tǒng)中寫入的redo日志量,這包括了寫到log buffer而沒有刷新到磁盤的日志, 相應(yīng)的,InnoDB提出了一個表示刷新到磁盤中的redo日志量的全局變量,稱之為flushed_to_disk_lsn。
系統(tǒng)第一次啟動時,該變量的值和初始的lsn值是相同的,都是 8704 。隨著系統(tǒng)的運行,redo日志被不斷寫入log buffer,但是并不會立即刷新到磁盤, lsn的值就和flushed_to_disk_lsn的值拉開了差距。我們演示一下:
系統(tǒng)第一次啟動后,向log buffer中寫入了 mtr_1、mtr_2、mtr_3這三個mtr產(chǎn)生的redo日志,假設(shè)這三個mtr開始和結(jié)束時對應(yīng)的lsn值分別是:
mtr_1:8716 ~ 8916。
mtr_2:8916 ~ 9948。
mtr_3:9948 ~ 10000。
此時的lsn 已經(jīng)增長到了10000,但是由于沒有刷新操作,所以此時flushed_to_disk_lsn的值仍為 8704 ,如圖:
隨后進(jìn)行將log buffer中的block刷新到redo日志文件的操作,假設(shè)將 mtr_1和mtr_2的日志刷新到磁盤,那么flushed_to_disk_lsn就應(yīng)該增長 mtr_1和mtr_2寫入的日志量,所以flushed_to_disk_lsn的值增長到了9948,如圖:
綜上所述,當(dāng)有新的redo 日志寫入到log buffer 時,首先lsn的值會增長,但flushed_to_disk_lsn不變, 隨后隨著不斷有l(wèi)og buffer中的日志被刷新到磁盤上, flushed_to_disk_lsn的值也跟著增長。如果兩者的值相同時,說明log buffer中的所有redo日志都已經(jīng)刷新到磁盤中了。
2. lsn值和redo日志文件偏移量的對應(yīng)關(guān)系
因為lsn的值是代表系統(tǒng)寫入的redo日志量的一個總和,一個mtr中產(chǎn)生多少日志,lsn的值就增加多少,這樣 mtr 產(chǎn)生的日志寫到磁盤中時,很容 易計算某一個lsn值在redo日志文件組中的偏移量,如圖:
初始時的LSN值是8704,對應(yīng)文件偏移量2048,之后每個mtr向磁盤中寫入多少字節(jié)日志,lsn的值就增長多少。
3. lsn值和redo日志文件偏移量的對應(yīng)關(guān)系
我們知道一個mtr代表一次對底層頁面的原子訪問,在訪問過程中可能會產(chǎn)生一組不可分割的redo日志,在mtr結(jié)束時,會把這一組redo日志寫入到log buffer中。除此之外,在mtr結(jié)束時還要把在mtr執(zhí)行過程中可能修改過的頁面加入到Buffer Pool的flush鏈表。我們看一下圖:
當(dāng)?shù)谝淮涡薷哪硞€緩存在Buffer Pool中的頁面時,就會把這個頁面對應(yīng)的控制塊插入到flush鏈表的頭部,之后再修改該頁面時由于它已經(jīng)在flush鏈表中了,就不再次插入了。也就是說flush鏈表中的臟頁是按照頁面的第一次修改時間從大到小進(jìn)行排序的。
在這個過程中會在緩存頁對應(yīng)的控制塊中記錄兩個關(guān)于頁面何時修改的屬性:
oldest_modification: 如果某個頁面被加載到Buffer Pool后進(jìn)行第一次修改,那么就將修改該頁面的mtr開始時對應(yīng)的lsn值寫入這個屬性。
newest_modification: 每修改一次頁面,都會將修改該頁面的mtr 結(jié)束時對應(yīng)的lsn值寫入這個屬性。也就是說該屬性表示頁面最近一次修改后對應(yīng)的系統(tǒng)lsn值。
我們接著上講的flushed_to_disk_lsn的例子看一下:
假設(shè)mtr_1執(zhí)行過程中修改了頁a ,那么在mtr_1執(zhí)行結(jié)束時,就會將頁a對應(yīng)的控制塊加入到flush鏈表的頭部。并且將mtr_1開始時對應(yīng)的lsn ,也就是8716 寫入頁a對應(yīng)的控制塊的oldest_modification屬性中,把 mtr_1結(jié)束時對應(yīng)的lsn,也就是8916寫入頁a對應(yīng)的控制塊的 newest_modification屬性中。畫個圖表示一下(為了讓美觀一些,我們把 oldest_modification 縮寫 成了o_m,把newest_modification 縮寫成了 n_m):
接著假設(shè)mtr_2執(zhí)行過程中又修改了頁b和頁c兩個頁面,那么在mtr_2執(zhí)行結(jié)束時,就會將頁b和頁c對應(yīng)的控制塊都加入到flush鏈表的頭部。并且將mtr_2開始時對應(yīng)的lsn,也就是8916寫入頁b和頁c對應(yīng)的控制塊的oldest_modification屬性中,把 mtr_2 結(jié)束時對應(yīng)的lsn,也就是9948寫入頁b和頁c對應(yīng)的控制塊的newest_modification屬性中。畫個圖表示一下:
接著假設(shè)mtr_3執(zhí)行過程中修改了頁b和頁d,不過頁b之前已經(jīng)被修改過了,所以它對應(yīng)的控制塊已經(jīng)被插入到了flush鏈表,所以在mtr_3執(zhí)行結(jié)束時,只需要將頁d對應(yīng)的控制塊都加入到flush鏈表的頭部即可。所以需要將mtr_3開始時對應(yīng)的lsn,也就是9948寫入頁d對應(yīng)的控制塊的 oldest_modification屬性中,把 mtr_3結(jié)束時對應(yīng)的lsn,也就是10000寫入頁d對應(yīng)的控制塊的newest_modification屬性中。另外,由于頁b在 mtr_3 執(zhí)行過程中又發(fā)生了一次修改,所以需要更新頁 b對應(yīng)的控制塊中newest_modification的值為10000。畫個圖表示一下:
綜上所述,flush鏈表中的臟頁按照修改發(fā)生的時間順序進(jìn)行排序,也就是按照oldest_modification代表的LSN值進(jìn)行排序,被多次更新的頁面不會重復(fù)插入到flush鏈表中,但是會更新newest_modification屬性的值。
三、checkpoint
我們要考慮一件事,就是redo日志文件組容量是有限的,我們不得不選擇循環(huán)使用redo日志文件組中的文件,但是這會造成最后寫的redo日志與最開始寫的redo日志追尾,這時應(yīng)該想到:redo日志只是為了系統(tǒng)崩潰后恢復(fù)臟頁用的,如果對應(yīng)的臟頁已經(jīng)刷新到了磁盤,也就是說即使現(xiàn)在系統(tǒng)崩潰,那么在重啟后也用不著使用redo日志恢復(fù)該頁面了,所以該redo日志也就沒有存在的必要了,那么它占用的磁盤空間就可以被后續(xù)的 redo日志所重用。也就是說:判斷某些redo日志占用的磁盤空間是否可以覆蓋的依據(jù)就是它對應(yīng)的臟頁是否已經(jīng)刷新到磁盤里。我們看一下前邊一直說的那個例子:
如圖,雖然mtr_1和mtr_2生成的redo日志都已經(jīng)被寫到了磁盤上,但是它們修改的臟頁仍然留在Buffer Pool中,所以它們生成的redo日志在磁盤上的空間是不可以被覆蓋的。之后隨著系統(tǒng)的運行,如果頁a被刷新到了磁盤,那么它對應(yīng)的控制塊就會從flush鏈表中移除,就像這樣子:
這樣mtr_1生成的redo日志就沒有用了,它們占用的磁盤空間就可以被覆蓋掉了。InnoDB提出了 一個全局變量checkpoint_lsn來代表當(dāng)前系統(tǒng)中可以被覆蓋的redo日志總量是多少,這個變量初始值也是8704 。比方說現(xiàn)在頁a被刷新到了磁盤,mtr_1生成的redo日志就可以被覆蓋了,所以我們可以進(jìn)行一個增加checkpoint_lsn的操作,我們把這個過程稱之為做一次 checkpoint 。做一次 checkpoint 其實可以分為兩個步驟:
步驟一:計算一下當(dāng)前系統(tǒng)中可以被覆蓋的redo日志對應(yīng)的lsn值最大是多少。redo 日志可以被覆蓋,意味著它對應(yīng)的臟頁被刷到了磁盤,只要我們計算出當(dāng)前系統(tǒng)中被最早修改的臟頁對應(yīng)的oldest_modification 值,那凡是在系統(tǒng)lsn值小于該節(jié)點的oldest_modification值時產(chǎn)生的redo日志都是可以被覆蓋掉的,我們就把該臟頁的oldest_modification 賦值給 checkpoint_lsn 。
比方說當(dāng)前系統(tǒng)中頁a已經(jīng)被刷新到磁盤,那么flush鏈表的尾節(jié)點就是頁c,該節(jié)點就是當(dāng)前系統(tǒng)中最早修改的臟頁了,它的oldest_modification值為8916,我們就把8916賦值給checkpoint_lsn。
步驟二:將checkpoint_lsn 和對應(yīng)的redo日志文件組偏移量以及此次 checkpint 的編號寫到日志文件的管理信息(就是checkpoint1 或者 checkpoint2 )中。InnoDB維護(hù)了一個目前系統(tǒng)做了多少次checkpoint 的變量 checkpoint_no,每做一次 checkpoint,該變量的值就加1。我們前邊說過計算一個lsn值對應(yīng)的redo日志文件組偏移量是很容易的,所以可以計算得到該checkpoint_lsn在redo日志文件組中對應(yīng)的偏移量 checkpoint_offset ,然后 把這三個值都寫到redo 日志文件組的管理信息中。
每一個redo日志文件都有2048 個字節(jié)的管理信息,但是上述關(guān)于checkpoint的信息只會被寫到日志文件組的第一個日志文件的管理信息中。不過我們是存儲到checkpoint1中還是checkpoint2中呢?InnoDB 規(guī)定,當(dāng)checkpoint_no的值是偶數(shù)時,就寫到checkpoint1中,是奇數(shù)時,就寫到checkpoint2中。記錄完checkpoint的信息之后, redo日志文件組中各個lsn值的關(guān)系就像這樣:
四、 用戶線程批量從flush鏈表中刷出臟頁
一般情況下都是后臺的線程在對LRU鏈表和flush鏈表進(jìn)行刷臟操作, 這主要因為刷臟操作比較慢,不想影響用戶線程處理請求。但是如果當(dāng)前系統(tǒng)修改頁面的操作十分頻繁,這樣就導(dǎo)致寫日志操作十分頻繁,系統(tǒng)lsn值增長過快。如果后臺的刷臟操作不能將臟頁刷出,那么系統(tǒng)無法及時做 checkpoint ,可能就需要用戶線程同步的從flush鏈表中把那些最早修改的臟頁( oldest_modification最小的臟頁)刷新到磁盤,這樣這些臟頁對應(yīng)的redo日志就沒用了,然后就可以去做checkpoint了。
五、 查看系統(tǒng)中的各種LSN值
我們可以使用SHOW ENGINE INNODB STATUS命令查看當(dāng)前 InnoDB存儲引擎中的各種LSN值的情況,比如:
mysql> SHOW ENGINE INNODB STATUS\G (...省略前邊的許多狀態(tài)) LOG --- Log sequence number 124476971 Log flushed up to 124099769 Pages flushed up to 124052503 Last checkpoint at 124052494 0 pending log flushes, 0 pending chkp writes 24 log i/o's done, 2.00 log i/o's/second ---------------------- (...省略后邊的許多狀態(tài))
Log sequence number: 代表系統(tǒng)中的 lsn 值,也就是當(dāng)前系統(tǒng)已經(jīng)寫入的redo日志量,包括寫入log buffer中的日志。
Log flushed up to: 代表flushed_to_disk_lsn的值,也就是當(dāng)前系統(tǒng)已經(jīng)寫入磁盤的redo日志量。
Pages flushed up to: 代表 flush鏈表中被最早修改的那個頁面對應(yīng)的oldest_modification屬性值。
Last checkpoint at: 當(dāng)前系統(tǒng)的checkpoint_lsn值。
六、 innodb_flush_log_at_trx_commit的用法
為了保證事務(wù)的持久性,用戶線程在事務(wù)提交時需要將該事務(wù)執(zhí)行過程中產(chǎn)生的所有redo日志都刷新到磁盤上。這一條要求太狠了,會很明顯的降低數(shù)據(jù)庫性能。如果對事務(wù)的持久性要求不是那么強烈的話,可以選擇修改一個稱為innodb_flush_log_at_trx_commit的系統(tǒng)變量的值,該變量有3個可選的值:
0 : 當(dāng)該系統(tǒng)變量值為0時,表示在事務(wù)提交時不立即向磁盤中同步redo日志,這個任務(wù)是交給后臺線程 做的。這樣很明顯會加快請求處理速度,但是如果事務(wù)提交后服務(wù)器掛了,后臺線程沒有及時將redo日志刷新到 磁盤,那么該事務(wù)對頁面的修改會丟失。
1 : 當(dāng)該系統(tǒng)變量值為1時,表示在事務(wù)提交時需要將redo日志同步到磁盤,可以保證事務(wù)的持久性。1也是innodb_flush_log_at_trx_commit 的默認(rèn)值。
2 : 當(dāng)該系統(tǒng)變量值為2時,表示在事務(wù)提交時需要將redo日志寫到操作系統(tǒng)的緩沖區(qū)中,但并不需要保 證將日志真正的刷新到磁盤。這種情況下如果數(shù)據(jù)庫掛了,操作系統(tǒng)沒掛的話,事務(wù)的持久性還是可以保證的,但是操作系統(tǒng)也掛了的話,那就不能保證持久性了。
七、崩潰恢復(fù)
在服務(wù)器不掛的情況下,redo日志不僅沒用,反而讓性能變得更差。但是當(dāng)數(shù)據(jù)庫掛了,我們就可以在重啟時根據(jù)redo日志中的記錄就可以將頁面恢復(fù)到系統(tǒng)崩潰前的狀態(tài)。我們接下來大致看一下恢復(fù)過程。
1. 確定恢復(fù)的起點
checkpoint_lsn之前的redo日志都可以被覆蓋,也就是說這些 redo 日志對應(yīng)的臟頁都已經(jīng)被刷新到磁盤中了,既然它們已經(jīng)被刷盤,我們就沒必要恢復(fù)它們了。對于checkpoint_lsn之后的redo日志,它們對應(yīng)的臟頁可能沒被刷盤,也可能被刷盤了,我們不能確定,所以需要從checkpoint_lsn開始讀取redo日志來恢復(fù)頁面。
當(dāng)然,redo 日志文件組的第一個文件的管理信息中有兩個block都存儲了checkpoint_lsn的信息,我們當(dāng)然是要選取最近發(fā)生的那次checkpoint的信息。衡量checkpoint發(fā)生時間早晚的信息就是所謂的 checkpoint_no, 我們只要把checkpoint1和checkpoint2這兩個block中的 checkpoint_no值讀出來比一下大小,哪個checkpoint_no值更大,說明哪個block存儲的就是最近的一次checkpoint信息。這樣我們就能拿到最近發(fā)生的checkpoint 對應(yīng)的checkpoint_lsn值以及它在 redo 日志文件組中的偏移量checkpoint_offset。
2. 確定恢復(fù)的終點
redo日志恢復(fù)的起點確定了,那終點是哪個呢?這個還得從block的結(jié)構(gòu)說起。我們說在寫redo日志的時候都是順序?qū)懙?,寫滿了一個block之后會再往下一個block中寫:
普通block的 log block header部分有一個稱之為 LOG_BLOCK_HDR_DATA_LEN的屬性,該屬性值記錄了當(dāng)前block里使用了多少字節(jié)的空間。對于被填滿的block來說,該值永遠(yuǎn)為512 。如果該屬性的值不為512,那么它就是此次崩潰恢復(fù)中需要掃描的最后一個block。
3. 怎么恢復(fù)
確定了需要掃描哪些redo 日志進(jìn)行崩潰恢復(fù)之后,接下來就是怎么進(jìn)行恢復(fù)了。假設(shè)現(xiàn)在的redo日志文件中有5條redo日志,如圖:
由于redo 0在checkpoint_lsn后邊,恢復(fù)時可以不管它。我們現(xiàn)在可以按照redo日志的順序依次掃描checkpoint_lsn之后的各條redo日志,按照日志中記載的內(nèi)容將對應(yīng)的頁面恢復(fù)出來。這樣沒什么問題,不過InnoDB還是想了一些辦法加快這個恢復(fù)的過程:
使用哈希表: 根據(jù)redo日志的space ID和page number屬性計算出散列值,把space ID和page number相同的redo日志放到哈希表的同一個槽里,如果有多個space ID和page number都相同的redo日志,那么它們之間使用鏈表連接起來,按照生成的先后順序鏈接起來的,如圖所示:
之后就可以遍歷哈希表,因為對同一個頁面進(jìn)行修改的redo日志都放在了一個槽里,所以可以一次性將一個頁面修復(fù)好,這樣可以加快恢復(fù)速度。
另外需要注意一點的是,同一個頁面的redo日志是按照生成時間順序進(jìn)行排序的,所以恢復(fù)的時候也是按照這個順序進(jìn)行恢復(fù),如果不按照生成時間順序進(jìn)行排序的話,那么可能出現(xiàn)錯誤。比如原先的修改操作是先插入一條記錄,再刪除該條記錄,如果恢復(fù)時不按照這個順序來,就可能變成先刪除一條記錄,再插入一條記錄,這顯然是錯誤的。
跳過已經(jīng)刷新到磁盤的頁面: checkpoint_lsn之前的redo日志對應(yīng)的臟頁確定都已經(jīng)刷到磁盤了,但是 checkpoint_lsn之后的redo日志我們不能確定是否已經(jīng)刷到磁盤,主要是因為在最近做的一次checkpoint后,可能后臺線程又不斷的從LRU鏈表和flush鏈表中將一些臟頁刷出Buffer Pool 。這些 在checkpoint_lsn之后的redo日志,如果它們對應(yīng)的臟頁在崩潰發(fā)生時已經(jīng)刷新到磁盤,那在恢復(fù)時也就沒有必要根據(jù)redo 日志的內(nèi)容修改該頁面了。
那在恢復(fù)時怎么知道某個redo日志對應(yīng)的臟頁是否在崩潰發(fā)生時已經(jīng)刷新到磁盤了呢?這還得從頁面的結(jié)構(gòu)說起,我們知道每個頁面都有一個稱之為File Header的部分,在File Header里有一個稱之為 FIL_PAGE_LSN的屬性,該屬性記載了最近一次修改頁面時對應(yīng)的lsn值(其實就是頁面控制塊中的newest_modification值)。如果在做了某次 checkpoint之后有臟頁被刷新到磁盤中,那么該頁對應(yīng)的FIL_PAGE_LSN 代表的lsn值肯定大于checkpoint_lsn的值,凡是符合這種情況的頁面就不需要重復(fù)執(zhí)行 lsn值小于 FIL_PAGE_LSN 的redo日志了,所以更進(jìn)一步提升了崩潰恢復(fù)的速度。
八、 遺漏的問題:LOG_BLOCK_HDR_NO是如何計算的
我們前邊說過,對于實際存儲redo日志的普通的log block來說,在 log block header處有一個稱之為LOG_BLOCK_HDR_NO的屬性,我們說這個屬性代表一個唯一的標(biāo)號。這個屬性是初次使用該block時分配的,跟當(dāng)時的系統(tǒng)lsn值有關(guān)。使用下邊的公式計算該block的 LOG_BLOCK_HDR_NO值:
((lsn / 512) & 0x3FFFFFFFUL) + 1
這個公式里的0x3FFFFFFFUL可能讓大家有點困惑,其實它的二進(jìn)制表示可能更親切一點:
從圖中可以看出,0x3FFFFFFFUL對應(yīng)的二進(jìn)制數(shù)的前2位為0,后30位的值都為1 。一個二進(jìn)制位與0做與運算(&)的結(jié)果肯定是0,一個二進(jìn)制位與1做與運算(&)的結(jié)果就是原值。讓一個數(shù)和0x3FFFFFFFUL 做與運算的意思就是要將該值的前2個比特位的值置為0,這樣該值就肯定小于或等于 0x3FFFFFFFUL了。
這也就說明了,不論lsn多大, ((lsn / 512) & 0x3FFFFFFFUL) 的值肯定在0到0x3FFFFFFFUL之間,再加1的話肯定在1到0x40000000UL之間。而0x40000000UL這個值就代表著1GB。也就是說系統(tǒng)最多能產(chǎn)生不重復(fù)的LOG_BLOCK_HDR_NO值只有1GB個。InnoDB規(guī)定redo日志文件組中包含的所有文件大小總和不得超過512GB,一個block大小是512字節(jié),也就是說redo日志文件組中包含的block塊最多為1GB個,所以有1GB個不重復(fù)的編號值也就夠用了。
另外,LOG_BLOCK_HDR_NO值的第一個比特位比較特殊,稱之為 flush bit,如果該值為1,代表著本block是在某次將log buffer中的block刷新到磁盤的操作中的第一個被刷入的block。
到此這篇關(guān)于mysql中 redo日志的文章就介紹到這了,更多相關(guān)mysql redo日志內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入理解MySQL重做日志 redo log
- mysql日志文件之undo?log和redo?log
- MySQL Redo與Undo日志詳細(xì)解析
- mysql日志系統(tǒng)redo log和bin log介紹
- MySQL三大日志(binlog、redo?log和undo?log)圖文詳解
- mysql中的事務(wù)重做日志(redo log)與回滾日志(undo log)
- 詳解MySQL事務(wù)日志redo log
- 真的了解MySQL中的binlog和redolog區(qū)別
- MySQL三大日志之binlog、redoLog、undoLog詳細(xì)講解
- MySQL8.0設(shè)置redo緩存大小的實現(xiàn)
相關(guān)文章
深入解析MySQL索引數(shù)據(jù)結(jié)構(gòu)
什么是索引?索引就是排好序的數(shù)據(jù)結(jié)構(gòu),可以幫助我們快速的查找到數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于MySQL索引數(shù)據(jù)結(jié)構(gòu)的相關(guān)資料,需要的朋友可以參考下2021-10-10MySQL聯(lián)合索引與最左匹配原則的實現(xiàn)
最左匹配原則在我們MySQL開發(fā)過程中和面試過程中經(jīng)常遇到,為了加深印象和理解,我在這里把MySQL的最左匹配原則詳細(xì)的講解一下,感興趣的可以了解一下2023-12-12用SELECT... INTO OUTFILE語句導(dǎo)出MySQL數(shù)據(jù)的教程
這篇文章主要介紹了用SELECT... INTO OUTFILE語句導(dǎo)出MySQL數(shù)據(jù)的教程,是MySQL入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-05-05MySQL性能優(yōu)化之table_cache配置參數(shù)淺析
這篇文章主要介紹了MySQL性能優(yōu)化之table_cache配置參數(shù)淺析,本文介紹了它的緩存機制、參數(shù)優(yōu)化及清空緩存的命令等,需要的朋友可以參考下2014-07-07Mysql?optimize?table?時報錯:Temporary?file?write?fail的解決
這篇文章主要介紹了Mysql?optimize?table?時報錯:Temporary?file?write?fail的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09將json文件數(shù)據(jù)導(dǎo)入到MySQL表中的詳細(xì)教程
如何使用json文件將數(shù)據(jù)導(dǎo)入到MySQL數(shù)據(jù)庫中的表里?Excel表格等文件的數(shù)據(jù)通過java或者python等語言讀取后生成一個json文件,然后想要將文件中的數(shù)據(jù)寫入到MySQL表中,本文介紹了將json文件數(shù)據(jù)導(dǎo)入到MySQL表中的詳細(xì)教程,需要的朋友可以參考下2024-07-07