系列文章

前言


咔咔閒談

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

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

一、redo log

第一步,建立一個表 user,主鍵是 id,下面是建立語句。

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

插入一條資料

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

若要將插入的這條資料的age改為26,則需要執行語句

update user set age = 26 where id = 1;

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

每個模組的功能可以回到第一期文章去檢視。

在MySQL8.0中redo log、binlog日誌檔案都位於/var/lib/mysql此目錄下,如圖

檔名為ib_logfile的是重做日誌,undo開頭的就是回滾日誌,對於回滾日誌後期進行詳細的討論。

redo log(重做日誌)是實現事務永續性必備要素,當一個事務提交後,並非直接修改資料庫的資料,而是首先保證在 redo log中記錄相關的操作。

Innodb儲存引擎中的redo log大小是固的,上圖顯示配置了一組兩個檔案,每個檔案大小預設為48M,使用innodb_log_file_size引數來控制單個檔案大小,在MySQL5.6.8以及之後版本都預設為48M。

然後redo log可以記錄48M的操作,redo log是一個閉環的迴圈寫。所設定的檔案個數和檔案大小不再增加。

write pos將記錄當前位置,同時向後移動,在ib-log-file-3檔案末尾後,然後返回ib-logfilg-0檔案開始寫。

check point記錄的是當前擦除的位置,要使檔案迴圈寫入,必須一邊擦除。清楚資料的前提是要將記錄更新到資料檔案。

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

你必須理解write pos的推進是因為在執行更新操作,這樣就不能再執行更新操作,直到記錄更新到資料檔案,然後check point進行擦除後才可以繼續執行更新操作。

對於innodb_log_file_size的設定也是有一些計算規則的,下面將為你介紹。

若innodb_log_file_size設定太小,將導致redo log檔案頻繁切換,頻繁的觸發資料庫的檢查點(check point),導致記錄更新到資料檔案的次數增加,從而影響IO效能。

同樣,如果有一個大的事務,並且所有 redo log日誌都已寫滿,但是還沒有完成,將導致日誌無法切換,從而導致 MySQL直接堵死。

innodb_log_file_size設定太大,雖然極大地提高了 IO效能,但是在 MySQL重啟或宕機時,恢復時間會因為 redo log檔案過大而延長。而這種恢復時間通常是無法控制的。

在設定合理的redo log大小和數量後,Innodb能夠保證,即使資料庫發生異常重啟,以前提交的記錄也不會丟失,這一點也稱為crash-safe。

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

二、如何根據專案情況設定innodb_log_file_size

對於引數innodb_log_files_in_group設定3~4個就夠用了,不用進行優化。

著重討論innodb_log_file_size的大小設定或優化設定。

在 MySQL8.0之前,通常是計算在一段時間內生成的事務日誌(redo log)大小,而 MySQL日誌檔案最小應承載一小時的業務日誌量。

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

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

目前,要檢視 sequence在一分鐘之內的值,您就可以執行 pager grep sequence,它對mysql> show engine innodb status\ G select sleep (60); show engine innodbstatus\ G;返回的結果。

禁止 pager設定執行 nopager,如果不執行該命令,則只有等到下一次重新啟動該命令才會失效。

此處咔咔是在虛擬機器上做的操作,可以看到一分鐘內是沒有任何操作,所以值前後相同,你可以在測試伺服器做測試。

這樣計算出來的select (後邊資料-前面的資料)/1024/1024*60 asMB_per_hour;值是一個小時後 redo log的大小

但是用這種方法計算一定是不合適的,在一分鐘內業務繁忙或者業務空閒時間計算出的值都會產生較大誤差。

合適的方法是在一天中確定幾個時間點,用一個指令碼定時執行,然後記錄相應的值,再取平均值,計算出的誤差將減至最小。

什麼是 sequece?
當每個 binlog生成時,該值從1開始,然後遞增,每增加一個事務, sequenumber就加上1。

二、binlog

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

server層當然是負責功能方面的,而儲存引擎層則負責處理與儲存相關的操作。

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

redo log和binlog的區別

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

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

redo log是迴圈寫的,並且空間是固定的,比如上面配置4個1GB的redo log檔案,binlog是追加寫的,這個檔案寫完了,換下一個檔案,不會覆蓋以前的日誌。這也就是你經常看到只要你有完整的binlog檔案就可以給你恢復到你想要的資料。

MySQL為什麼會有倆份日誌呢?

在沒有Innodb儲存引擎之前,MySQL預設儲存引擎是MyIsam,但MyIsam是沒有重啟恢復能力的,binlog日誌也僅用於歸檔。

Innodb是另一家公司以外掛的形式引入到Mysql,既然binlog沒有重啟恢復的能力,那麼我就使用redo log來實現重啟恢復的功能。

這就導致了當你使用Innodb儲存引擎時會寫倆份日誌。

三、什麼是兩階段提交

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

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

  • 執行器先到引擎層找到id = 1這一行,由於ID是主鍵,所以會在主鍵索引樹找到這一行。如果ID=2這一行所在的資料頁本來就在記憶體中,就直接返回給執行器。否則,需要先從磁碟中讀入記憶體,然後再返回。

  • 執行器拿到儲存引擎返回id = 2結果後,給age加上1,原來是25,現在就是26,在呼叫引擎介面寫入這行新資料。

  • 引擎將這行資料先更新到記憶體中,同時將這個更新操作記錄到redo log中,此時redo log處於prepare狀態。然後告知執行器執行完成了,隨時可以提交事務。

  • 接著執行器生成這個操作的binlog,並把binlog寫入磁碟。

  • 執行器呼叫引擎的提交事務介面,引擎把剛剛寫入的redo log改成提交commit狀態,更新完成。

到這裡你應該就清晰了,一條更新SQL會先寫redo log再寫binlog,這也就是標題為什麼叫一生摯友redo log、binlog

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

是為了讓redo log跟binlog兩份日誌之間的邏輯一致,看下面倆種情況。

先寫redo log後寫binlog

  • 更新語句為age = age +1
  • 將資料寫入redo log,MySQL程序異常重啟
  • 此時binlog還沒有開始寫
  • 系統重啟後進行資料恢復此時的值為26
  • 需要搭建從庫時需要拿binlog進行恢復資料,但此時age = age +1 這行的操作是沒有記錄到binlog的
  • 那麼此時的從庫就會少這一次的更新,恢復出來的age依然是25,造成於主庫資料不一致。

先寫binlog後寫redo log

  • 更新語句為age = age +1
  • 將資料寫入binlog,MySQL異常重啟
  • 此時redo log 還沒寫
  • MySQL系統重啟,這個更新操作是對於redo log是不存在的,所以重啟後的值依然是25
  • 但binlog 中的值已將是26了
  • 需要搭建從庫時,從庫的值是26,主庫的值是25,造成主從資料不一致

所以說,如果不使用兩階段提交,那麼原庫和用它的binlog日誌恢復出來的庫資料是不一致的。

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

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

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

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

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

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

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

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

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

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

畫一幅圖讓大家能更好的理解掌櫃、黑板、在MySQL中的對應關係。


酒店掌櫃於MySQL對應的關係

六、redo log引數詳解

事務的永續性就是通過重做日誌來實現的。

當提交事務之後,並不是直接修改資料庫的資料的,而是先保證將相關的操作記錄到redo日誌中。

資料庫會根據相應的機制將記憶體的中的髒頁資料重新整理到磁碟中。


重做日誌寫入流程

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

在上圖中提到倆個陌生概念,Buffer pool、redo log buffer,這個倆個都是Innodb儲存引擎的記憶體區域的一部分。

而redo log file是位於磁碟位置。

也就說當有DML(insert、update、delete)操作時,資料會先寫入Buffer pool,然後在寫到重做日誌緩衝區。

重做日誌緩衝區會根據刷盤機制來進行寫入重做日誌中。

這個機制的設定引數為innodb_flush_log_at_trx_commit,引數分別為0,1,2


刷盤策略

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

  • 當這個引數的值為0的時,提交事務之後,會把資料存放到redo log buffer中,然後每秒將資料寫進磁碟檔案
  • 當這個引數的值為1的時,提交事務之後,就必須把redo log buffer從記憶體刷入到磁碟檔案裡去,只要事務提交成功,那麼redo log就必然在磁盤裡了。
  • 當這個引數的值為2的情況,提交事務之後,把redo log buffer日誌寫入磁碟檔案對應的os cache快取裡去,而不是直接進入磁碟檔案,1秒後才會把os cache裡的資料寫入到磁碟檔案裡去。

伺服器異常停止對事務如何應對(事務寫入過程)

  • 當引數為0時,前一秒的日誌都儲存在日誌緩衝區,也就是記憶體上,如果機器宕掉,可能丟失1秒的事務資料。
  • 當引數為1時,資料庫對IO的要求就非常高了,如果底層的硬體提供的IOPS比較差,那麼MySQL資料庫的併發很快就會由於硬體IO的問題而無法提升。
  • 當引數為2時,資料是直接寫進了os cache快取,這部分屬於作業系統部分,如果作業系統部分損壞或者斷電的情況會丟失1秒內的事務資料,這種策略相對於第一種就安全了很多,並且對IO要求也沒有那麼高。

小結

關於效能:0>2>1

關於安全:1>2>0

根據以上結論,所以說在MySQL資料庫中,刷盤策略預設值為1,保證事務提交之後,資料絕對不會丟失。

堅持學習、堅持寫作、堅持分享是咔咔從業以來所秉持的信念。願文章在偌大的網際網路上能給你帶來一點幫助,我是咔咔,下期見。