一生摯友redo log、binlog《死磕MySQL系列 二》

系列文章

前言

咔咔閑談

上期根據(jù)一條查詢語句查詢流程分析MySQL的整體架構(gòu)。同樣,本期也使用一條查詢SQL語句來做引子。可以肯定的是,查詢語句執(zhí)行的流程更新語句同樣也會執(zhí)行。

因此本期的著重點就不在MySQL架構(gòu)圖上,文章標(biāo)題也給出了大家重點,就是要了解redo log、binlog。

一、redo log

第一步,創(chuàng)建一個表 user,主鍵是 id,下面是創(chuàng)建語句。

CREATE TABLE `user` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) NOT NULL,
 `age` tinyint(4) NOT NULL,
 `time` int(11) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

插入一條數(shù)據(jù)

insert into user (`name`,`age`,`time`) values ("咔咔","25",unix_timestamp(now()))

若要將插入的這條數(shù)據(jù)的age改為26,則需要執(zhí)行語句

update user set age = 26 where id = 1;

第一期文章中提到一條查詢語句的執(zhí)行流程,該流程與更新語句相同。這里將那幅圖拿過來在熟悉一下。

image

每個模塊的功能可以回到第一期文章去查看。

在MySQL8.0中redo log、binlog日志文件都位于/var/lib/mysql此目錄下,如圖

image

文件名為ib_logfile的是重做日志,undo開頭的就是回滾日志,對于回滾日志后期進行詳細的討論。

redo log(重做日志)是實現(xiàn)事務(wù)持久性必備要素,當(dāng)一個事務(wù)提交后,并非直接修改數(shù)據(jù)庫的數(shù)據(jù),而是首先保證在 redo log中記錄相關(guān)的操作。

Innodb存儲引擎中的redo log大小是固的,上圖顯示配置了一組兩個文件,每個文件大小默認為48M,使用innodb_log_file_size參數(shù)來控制單個文件大小,在MySQL5.6.8以及之后版本都默認為48M。

image

然后redo log可以記錄48M的操作,redo log是一個閉環(huán)的循環(huán)寫。所設(shè)定的文件個數(shù)和文件大小不再增加。

image

write pos將記錄當(dāng)前位置,同時向后移動,在ib-log-file-3文件末尾后,然后返回ib-logfilg-0文件開始寫。

check point記錄的是當(dāng)前擦除的位置,要使文件循環(huán)寫入,必須一邊擦除。清楚數(shù)據(jù)的前提是要將記錄更新到數(shù)據(jù)文件。

上面的綠色部分就是可寫的部分,假設(shè)如果 writepos追上了 checkpoint,那該怎么辦?

你必須理解write pos的推進是因為在執(zhí)行更新操作,這樣就不能再執(zhí)行更新操作,直到記錄更新到數(shù)據(jù)文件,然后check point進行擦除后才可以繼續(xù)執(zhí)行更新操作。

對于innodb_log_file_size的設(shè)置也是有一些計算規(guī)則的,下面將為你介紹。

若innodb_log_file_size設(shè)置太小,將導(dǎo)致redo log文件頻繁切換,頻繁的觸發(fā)數(shù)據(jù)庫的檢查點(check point),導(dǎo)致記錄更新到數(shù)據(jù)文件的次數(shù)增加,從而影響IO性能。

同樣,如果有一個大的事務(wù),并且所有 redo log日志都已寫滿,但是還沒有完成,將導(dǎo)致日志無法切換,從而導(dǎo)致 MySQL直接堵死。

innodb_log_file_size設(shè)置太大,雖然極大地提高了 IO性能,但是在 MySQL重啟或宕機時,恢復(fù)時間會因為 redo log文件過大而延長。而這種恢復(fù)時間通常是無法控制的。

在設(shè)置合理的redo log大小和數(shù)量后,Innodb能夠保證,即使數(shù)據(jù)庫發(fā)生異常重啟,以前提交的記錄也不會丟失,這一點也稱為crash-safe。

在這里,對crash-safe的理解先不提及它是什么,后面的文章會讓你明白。

二、如何根據(jù)項目情況設(shè)置innodb_log_file_size

對于參數(shù)innodb_log_files_in_group設(shè)置3~4個就夠用了,不用進行優(yōu)化。

著重討論innodb_log_file_size的大小設(shè)置或優(yōu)化設(shè)置。

在 MySQL8.0之前,通常是計算在一段時間內(nèi)生成的事務(wù)日志(redo log)大小,而 MySQL日志文件最小應(yīng)承載一小時的業(yè)務(wù)日志量。

此處的一段時間必須視自己的業(yè)務(wù)情況而定,外界有用1分鐘的日志量也有1小時的日志量來計算。

首先看一下 MySQL客戶端的一個命令 pager,在 MySQL日常操作中,通過設(shè)置 pager的顯示方式,可以大大提高工作效率。

目前,要查看 sequence在一分鐘之內(nèi)的值,您就可以執(zhí)行 pager grep sequence,它對mysql> show engine innodb status\ G select sleep (60); show engine innodbstatus\ G;返回的結(jié)果。

禁止 pager設(shè)置執(zhí)行 nopager,如果不執(zhí)行該命令,則只有等到下一次重新啟動該命令才會失效。

image

此處咔咔是在虛擬機上做的操作,可以看到一分鐘內(nèi)是沒有任何操作,所以值前后相同,你可以在測試服務(wù)器做測試。

這樣計算出來的select (后邊數(shù)據(jù)-前面的數(shù)據(jù))/1024/1024*60 asMB_per_hour;值是一個小時后 redo log的大小

但是用這種方法計算一定是不合適的,在一分鐘內(nèi)業(yè)務(wù)繁忙或者業(yè)務(wù)空閑時間計算出的值都會產(chǎn)生較大誤差。

合適的方法是在一天中確定幾個時間點,用一個腳本定時執(zhí)行,然后記錄相應(yīng)的值,再取平均值,計算出的誤差將減至最小。

什么是 sequece?
當(dāng)每個 binlog生成時,該值從1開始,然后遞增,每增加一個事務(wù), sequenumber就加上1。

二、binlog

您可以從總體上了解到 MySQL架構(gòu)分為兩層,一個是 server層,另一個是存儲引擎層。

server層當(dāng)然是負責(zé)功能方面的,而存儲引擎層則負責(zé)處理與存儲相關(guān)的操作。

而且上面提到的redo log是Innodb存儲引擎層特有的,其它存儲引擎是不具備的,而server層也有自己的日志記錄,就是將要聊到的binlog。

redo log和binlog的區(qū)別

redo log是Innodb引擎特有的,而binlog是MySQLserver層特有的,所有引擎都可以使用。

redo log是物理日志,它記錄的是一條更新操作所做的修改,binlog是邏輯日志,記錄的是一條更新語句執(zhí)行邏輯

redo log是循環(huán)寫的,并且空間是固定的,比如上面配置4個1GB的redo log文件,binlog是追加寫的,這個文件寫完了,換下一個文件,不會覆蓋以前的日志。這也就是你經(jīng)??吹街灰阌型暾腷inlog文件就可以給你恢復(fù)到你想要的數(shù)據(jù)。

MySQL為什么會有倆份日志呢?

在沒有Innodb存儲引擎之前,MySQL默認存儲引擎是MyIsam,但MyIsam是沒有重啟恢復(fù)能力的,binlog日志也僅用于歸檔。

Innodb是另一家公司以插件的形式引入到Mysql,既然binlog沒有重啟恢復(fù)的能力,那么我就使用redo log來實現(xiàn)重啟恢復(fù)的功能。

這就導(dǎo)致了當(dāng)你使用Innodb存儲引擎時會寫倆份日志。

三、什么是兩階段提交

對redo log、binlog有了一定的認識后再來看看一條更新語句的執(zhí)行流程。

update user set age = age + 1 where id = 1;

  • 執(zhí)行器先到引擎層找到id = 1這一行,由于ID是主鍵,所以會在主鍵索引樹找到這一行。如果ID=2這一行所在的數(shù)據(jù)頁本來就在內(nèi)存中,就直接返回給執(zhí)行器。否則,需要先從磁盤中讀入內(nèi)存,然后再返回。

  • 執(zhí)行器拿到存儲引擎返回id = 2結(jié)果后,給age加上1,原來是25,現(xiàn)在就是26,在調(diào)用引擎接口寫入這行新數(shù)據(jù)。

  • 引擎將這行數(shù)據(jù)先更新到內(nèi)存中,同時將這個更新操作記錄到redo log中,此時redo log處于prepare狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時可以提交事務(wù)。

  • 接著執(zhí)行器生成這個操作的binlog,并把binlog寫入磁盤。

  • 執(zhí)行器調(diào)用引擎的提交事務(wù)接口,引擎把剛剛寫入的redo log改成提交commit狀態(tài),更新完成。

到這里你應(yīng)該就清晰了,一條更新SQL會先寫redo log再寫binlog,這也就是標(biāo)題為什么叫一生摯友redo log、binlog。

image

四、為什么需要兩階段提交

是為了讓redo log跟binlog兩份日志之間的邏輯一致,看下面?zhèn)z種情況。

先寫redo log后寫binlog

  • 更新語句為age = age +1
  • 將數(shù)據(jù)寫入redo log,MySQL進程異常重啟
  • 此時binlog還沒有開始寫
  • 系統(tǒng)重啟后進行數(shù)據(jù)恢復(fù)此時的值為26
  • 需要搭建從庫時需要拿binlog進行恢復(fù)數(shù)據(jù),但此時age = age +1 這行的操作是沒有記錄到binlog的
  • 那么此時的從庫就會少這一次的更新,恢復(fù)出來的age依然是25,造成于主庫數(shù)據(jù)不一致。

先寫binlog后寫redo log

  • 更新語句為age = age +1
  • 將數(shù)據(jù)寫入binlog,MySQL異常重啟
  • 此時redo log 還沒寫
  • MySQL系統(tǒng)重啟,這個更新操作是對于redo log是不存在的,所以重啟后的值依然是25
  • 但binlog 中的值已將是26了
  • 需要搭建從庫時,從庫的值是26,主庫的值是25,造成主從數(shù)據(jù)不一致

所以說,如果不使用兩階段提交,那么原庫和用它的binlog日志恢復(fù)出來的庫數(shù)據(jù)是不一致的。

五、《孔乙己》讓你明白redo log是什么

來看一個初中九年級語文課文中《孔乙己》這篇文章,就算不記得內(nèi)容,標(biāo)題總記得哈!

這個案例也是看丁老師文章中提到的,為什么丁老可以靈活的使用這個案例來講redo log而我們想不到呢?

其本質(zhì)原因是對知識點沒有理解透徹,使用生活案例來解釋技術(shù)是讓人最容易理解并不難遺忘的。

《孔乙己》中的主人公就叫他酒店掌柜,掌柜的有倆件法寶讓比其他老板工作效率高很多。一個是小黑板另一個是賬本。

試想一下如果有客人要賒賬,是直接寫到黑板效率高,還是翻密密麻麻的賬本來的快呢?

掌柜肯定會選擇先記錄到黑板上,等人少或者不忙時再把黑板的記錄寫到賬本中。

反之老板沒有黑板的話,只能在密密麻麻的賬本中先找到賒賬人的名字,如果之前有賒賬記錄追加,找了一遍發(fā)現(xiàn)沒有才進行新增。

這個過程不僅繁瑣而且效率低的讓人難以接受,如果酒店客人多老板是記錄不過來的。

同樣,在MySQL中也會存在這個問題,每次執(zhí)行更新語句都需要先找到那條記錄,然后再更新,整個過程IO成本、查找成本都很高。所以MySQL也利用了酒店掌柜的智慧使用黑板來提升執(zhí)行效率。

畫一幅圖讓大家能更好的理解掌柜、黑板、在MySQL中的對應(yīng)關(guān)系。

酒店掌柜于MySQL對應(yīng)的關(guān)系

六、redo log參數(shù)詳解

事務(wù)的持久性就是通過重做日志來實現(xiàn)的。

當(dāng)提交事務(wù)之后,并不是直接修改數(shù)據(jù)庫的數(shù)據(jù)的,而是先保證將相關(guān)的操作記錄到redo日志中。

數(shù)據(jù)庫會根據(jù)相應(yīng)的機制將內(nèi)存的中的臟頁數(shù)據(jù)刷新到磁盤中。

重做日志寫入流程

上圖是一個簡單的重做日志寫入流程。

在上圖中提到倆個陌生概念,Buffer pool、redo log buffer,這個倆個都是Innodb存儲引擎的內(nèi)存區(qū)域的一部分。

而redo log file是位于磁盤位置。

也就說當(dāng)有DML(insert、update、delete)操作時,數(shù)據(jù)會先寫入Buffer pool,然后在寫到重做日志緩沖區(qū)。

重做日志緩沖區(qū)會根據(jù)刷盤機制來進行寫入重做日志中。

這個機制的設(shè)置參數(shù)為innodb_flush_log_at_trx_commit,參數(shù)分別為0,1,2

刷盤策略

上圖即為重做日志的寫入策略。

  • 當(dāng)這個參數(shù)的值為0的時,提交事務(wù)之后,會把數(shù)據(jù)存放到redo log buffer中,然后每秒將數(shù)據(jù)寫進磁盤文件
  • 當(dāng)這個參數(shù)的值為1的時,提交事務(wù)之后,就必須把redo log buffer從內(nèi)存刷入到磁盤文件里去,只要事務(wù)提交成功,那么redo log就必然在磁盤里了。
  • 當(dāng)這個參數(shù)的值為2的情況,提交事務(wù)之后,把redo log buffer日志寫入磁盤文件對應(yīng)的os cache緩存里去,而不是直接進入磁盤文件,1秒后才會把os cache里的數(shù)據(jù)寫入到磁盤文件里去。

服務(wù)器異常停止對事務(wù)如何應(yīng)對(事務(wù)寫入過程)

  • 當(dāng)參數(shù)為0時,前一秒的日志都保存在日志緩沖區(qū),也就是內(nèi)存上,如果機器宕掉,可能丟失1秒的事務(wù)數(shù)據(jù)。
  • 當(dāng)參數(shù)為1時,數(shù)據(jù)庫對IO的要求就非常高了,如果底層的硬件提供的IOPS比較差,那么MySQL數(shù)據(jù)庫的并發(fā)很快就會由于硬件IO的問題而無法提升。
  • 當(dāng)參數(shù)為2時,數(shù)據(jù)是直接寫進了os cache緩存,這部分屬于操作系統(tǒng)部分,如果操作系統(tǒng)部分損壞或者斷電的情況會丟失1秒內(nèi)的事務(wù)數(shù)據(jù),這種策略相對于第一種就安全了很多,并且對IO要求也沒有那么高。

小結(jié)

關(guān)于性能:0>2>1

關(guān)于安全:1>2>0

根據(jù)以上結(jié)論,所以說在MySQL數(shù)據(jù)庫中,刷盤策略默認值為1,保證事務(wù)提交之后,數(shù)據(jù)絕對不會丟失。

堅持學(xué)習(xí)、堅持寫作、堅持分享是咔咔從業(yè)以來所秉持的信念。愿文章在偌大的互聯(lián)網(wǎng)上能給你帶來一點幫助,我是咔咔,下期見。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容