《MySQL》系列 - 十張圖詳解 MySQL 日誌(建議收藏)

01 前言

事情是這樣的,我負責我司的報表系統,小胖是我小弟。某天他手賤誤刪了一條生產的數據。被用戶在羣裏瘋狂投訴質問,火急火燎的跑來問我怎麼辦。我特麼冷汗都出來了,訓斥了他一頓:蠢,蠢得均可以進博物館了,生產的數據能隨便動?前端

小胖看我日常笑嘻嘻的,沒想到發這麼大的火。心一急,竟然給我跪下了:遠哥,我上有老,下有小,中有女友,不要開除我呀。我一聽火更大了:合着就你有女友???這個時候咱們 DBA 老林來打圓場:別慌,年輕人管不住下自己,不免作錯事。我能夠把數據恢復到一個月內任意時刻的狀態。聽到這,小胖忙抱着老林大腿哭爹喊娘地感謝。java

聽到這你是否是很奇怪?能恢復到半個月前的數據?DBA 老林究竟是如何作到的?我跟他細聊了一番。老林點燃了手中 82 年的華子,深深吸了一口說到:事情還得從 update 語句是如何執行的提及mysql

1.1 從更新語句提及

假設我如今有建表語句,以下:面試

CREATE TABLE `student`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `age` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 66 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
複製代碼

表數據以下:算法

表數據

今天剛好張三生日,我要把它的 age 加一歲。因而執行如下的 sql 語句:sql

update student set age = age + 1 where id = 2;
複製代碼

前面聊過查詢語句是如何執行的?錯過的同窗看這篇《工做三年:小胖連 select 語句是如何執行的都不知道,真的菜!》,裏面的查詢語句流程,更新語句也會走一遍,以下流程圖:數據庫

Mysql架構圖

update 語句發起:首先鏈接器會鏈接數據庫。接着分析器經過詞法、語法分析知道這是更新語句。因此查詢緩存失效。編程

以前的文章提到:若是表有更新。那麼它的查詢緩存會失敗。這也是爲啥,我不建議你使用查詢緩存的緣由設計模式

優化器則決定使用 ID 索引去更新,最後執行器負責找到這行數據,執行更新。緩存

重點來了:與查詢流程不同,更新還涉及兩個重要的日誌模塊。一是重作日誌: redo log,二是歸檔日誌:binlog。要解答文章開頭的問題,必需要明白這兩日誌的原理才能整明白 DBA 是怎麼作到的。

02 事務日誌:redo log

什麼是 redo log?爲了方便理解,先舉個來自極客時間的例子:

還記得《孔乙己》這篇文章,飯店掌櫃有一個粉板,專門用來記錄客人的賒帳記錄。若是賒帳的人很少,那麼他能夠把顧客名和帳目寫在板上。但若是賒帳的人多了,粉板總會有記不下的時候,這個時候掌櫃必定還有一個專門記錄賒帳的帳本

若是有人要賒帳或者還帳的話,掌櫃通常有兩種作法:

  • 一種作法是直接把帳本翻出來,把此次賒的帳加上去或者扣除掉;

  • 另外一種作法是先在粉板上記下此次的帳,等打烊之後再把帳本翻出來覈算。

在生意紅火櫃檯很忙時,掌櫃必定會選擇後者,由於前者操做實在是太麻煩了。首先,你得找到這我的的賒帳總額那條記錄。你想一想,密密麻麻幾十頁,掌櫃要找到那個名字,可能還得帶上老花鏡慢慢找,找到以後再拿出算盤計算,最後再將結果寫回到帳本上。

這整個過程想一想都麻煩。相比之下,仍是先在粉板上記一下方便。你想一想,若是掌櫃沒有粉板的幫助,每次記帳都得翻帳本,效率是否是低得讓人難以忍受?

2.1 爲何須要 redo log?

一樣,在 MySQL 中,若是每一次的更新要寫進磁盤,這麼作會帶來嚴重的性能問題:

  • 由於 Innodb 是以頁爲單位進行磁盤交互的,而一個事務極可能只修改一個數據頁裏面的幾個字節,這時將完整的數據頁刷到磁盤的話,太浪費資源了!
  • 一個事務可能涉及修改多個數據頁,而且這些數據頁在物理上並不連續,使用隨機 IO 寫入性能太差

爲了解決這個問題,MySQL 的設計者就用了相似掌櫃粉板的思路來提高更新效率。這種思路在 MySQL 中叫 WAL(Write-Ahead Logging),意思就是:先寫 redo log 日誌,後寫磁盤。日誌和磁盤就對應上面的粉板和帳本。

具體到 MySQL 是這樣的:有記錄須要更新,InnDB 把記錄寫到 redo log 中,並更新內存中的數據頁,此時更新就算完成。同時,後臺線程會把操做記錄更新異步到磁盤中的數據頁。

PS:當須要更新的數據頁在內存中時,就會直接更新內存中的數據頁;不在內存中時,在可使用 change buffer(篇幅有限,這個後面寫文章再聊) 的狀況下,就會將更新操做記錄到 change buffer 中,並將這些操做記錄到 redo log 中;若是此時有查詢操做,則觸發 merge 操做,返回更改後的記錄值

有些人說 InnoDB 引擎把日誌記錄寫到 redo log 中,redo log 在哪,不也是在磁盤上麼?

對,這也是一個寫磁盤的過程,可是與更新過程不同的是,更新過程是在磁盤上隨機 IO,費時。 而寫 redo log 是在磁盤上順序 IO,效率要高

PPS:redo log 的存在就是把全局的隨機寫,變換爲局部的順序寫,從而提升效率

2.2 redo log 的寫入過程

redo log 記錄了事務對數據頁作了哪些修改。它包括兩部分:分別是內存中的日誌緩衝(redo log buffer)和磁盤上的日誌文件(redo logfile)。

mysql 每執行一條 DML 語句,先將記錄寫入 redo log buffer,後續某個時間點再一次性將多個操做記錄寫到 redo log file。也就是咱們上面提到的 WAL 技術。

計算機操做系統告訴咱們:用戶空間下的緩衝區數據是沒法直接寫入磁盤的。由於中間必須通過操做系統的內核空間緩衝區(OS Buffer)。

因此,redo log buffer 寫入 redo logfile 其實是先寫入 OS Buffer,而後操做系統調用 fsync() 函數將日誌刷到磁盤。過程以下:

redo log 的寫入過程

mysql 支持三種將 redo log buffer 寫入 redo log file 的時機,能夠經過 innodb_flush_log_at_trx_commit 參數配置,各參數值含義以下:建議設置成1,這樣能夠保證MySQL 異常重啓以後數據不丟失

參數值 含義
0(延遲寫) 事務提交時不會將 redo log buffer 中日誌寫到 os buffer,而是每秒寫入os buffer 並調用 fsync() 寫入到 redo logfile 中。也就是說設置爲 0 時是(大約)每秒刷新寫入到磁盤中的,當系統崩潰,會丟失1秒鐘的數據。
1(實時寫、實時刷新) 事務每次提交都會將 redo log buffer 中的日誌寫入 os buffer 並調用 fsync() 刷到 redo logfile 中。這種方式即便系統崩潰也不會丟失任何數據,可是由於每次提交都寫入磁盤,IO的性能差。
2(實時寫、延遲刷新刷新) 每次提交都僅寫入到 os buffer,而後是每秒調用 fsync() 將 os buffer 中的日誌寫入到 redo log file。

寫的過程以下:

redo log buffer 寫入 redo log file 的時機

2.3 redo log file 的結構

InnoDB 的 redo log 是固定大小的。好比能夠配置爲一組 4 個文件,每一個文件的大小是 1GB,那麼 redo log file 能夠記錄 4GB 的操做。從頭開始寫。寫到末尾又回到開頭循環寫。以下圖:

redo log file 的結構

上圖中,write pos 表示 redo log 當前記錄的 LSN (邏輯序列號) 位置,一邊寫一遍後移,寫到第 3 號文件末尾後就回到 0 號文件開頭; check point 表示數據頁更改記錄刷盤後對應 redo log 所處的 LSN(邏輯序列號) 位置,也是日後推移而且循環的。

PS:check point 是當前要擦除的位置,它與數據頁中的 LSN 應當是一致的

write pos 到 check point 之間的部分是 redo log 的未寫區域,可用於記錄新的記錄;check point 到 write pos 之間是 redo log 已寫區域,是待刷盤的數據頁更改記錄。

當 write pos 追上 check point 時,表示 redo log file 寫滿了,這時候有就不能執行新的更新。得停下來先擦除一些記錄(擦除前要先把記錄刷盤),再推進 check point 向前移動,騰出位置再記錄新的日誌。

2.4 什麼是 crash-save ?

有了 redo log ,即在 InnoDB 存儲引擎中,事務提交過程當中任何階段,MySQL 忽然奔潰,重啓後都能保證事務的完整性,已提交的數據不會丟失,未提交完整的數據會自動進行回滾。這個能力稱爲 crash-safe,依賴的就是 redo log 和 undo log 兩個日誌。

好比:重啓 innodb 時,首先會檢查磁盤中數據頁的 LSN ,若是數據頁的 LSN 小於日誌中 check point 的 LSN ,則會從 checkpoint 開始恢復。

2.5 回滾日誌 undo log

**undo log,主要提供回滾的做用,但還有另外一個做用,就是多個行版本控制 (MVCC),保證事務的原子性。**在數據修改的流程中,會記錄一條與當前操做相反的邏輯日誌到 undo log 中(能夠認爲當 delete 一條記錄時,undo log 中會記錄一條對應的 insert 記錄,反之亦然,當 update 一條記錄時,它記錄一條對應相反的 update 記錄),若是由於某些緣由致使事務異常失敗了,能夠藉助該 undo log 進行回滾,保證事務的完整性,因此 undo log 也必不可少。

03 歸檔日誌:binlog

上一篇聊查詢語句的執行過程時,聊到 MySQL 的架構包含 server 層和引擎層。而 redo log 是 InnoDB 引擎特有的日誌,而 server 層也有本身的日誌,那就是 binlog。

最開始 MySQL 裏並無 InnoDB 引擎。MySQ L自帶的引擎是 MyISAM,可是 MyISAM 沒有 crash-safe 的能力,binlog 日誌只能用於歸檔。而 InnoDB 是另外一個公司以插件形式引入MySQL 的,只依靠 binlog 是沒有 crash-safe 能力的,因此 InnoDB 使用另一套日誌系統——也就是 redo log 來實現 crash-safe 能力。

3.1 binlog 日誌格式?

binlog 有三種格式,分別爲 STATMENT 、 ROW 和 MIXED。

在 MySQL 5.7.7 以前,默認的格式是 STATEMENT , MySQL 5.7.7 以後,默認值是 ROW。日誌格式經過 binlog-format 指定。

  • STATMENT:每一條會修改數據的 sql 語句會記錄到 binlog 中 。
  • ROW:不記錄 sql 的上下文信息,僅需記錄哪條數據被修改。記兩條,更新前和更新後都有。
  • MIXED:前兩種模式的混合,通常的複製使用 STATEMENT 模式保存 binlog ,對於 STATEMENT 模式沒法複製的操做使用 ROW 模式保存 binlog

3.2 binlog 能夠作 crash-save 嗎?

只用一個 binlog 是否能夠實現 cash_safe 能力呢?答案是能夠的,只不過 binlog 中也要加入 checkpoint,數據庫故障重啓後,binlog checkpoint 以後的 sql 都重放一遍。可是這樣作讓 binlog 耦合的功能太多。

有人說,也能夠直接直接對比匹配全量 binlog 和磁盤數據庫文件,但這樣作的話,效率低不說。由於 binlog 是 server 層的記錄並非引擎層的,有可能致使數據不一致的狀況:

假如 binlog 記錄了 3 條數據,正常狀況引擎層也寫了 3 條數據,可是此時節點宕機重啓,binlog 發現有 3 條記錄須要回放,因此回放 3 條記錄,可是引擎層可能已經寫入了 2 條數據到磁盤,只須要回放一條 1 數據。那 binlog 回放的前兩條數據會不會重複呢,好比會報錯 duplicate key。

另外,binlog 是追加寫,crash 時不能斷定 binlog 中哪些內容是已經寫入到磁盤,哪些還沒被寫入。而 redolog 是循環寫,從 check point 到 write pos 間的內容都是未寫入到磁盤的。

因此,binlog 並不適合作 crash-save。

3.3 兩種日誌的區別

redo log 和 binlog 主要有三種不一樣:

  • redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,全部引擎均可以使用。
  • redo log 是物理日誌,記錄的是在某個數據頁上作了什麼修改;binlog 是邏輯日誌,記錄的是這個語句的原始邏輯,好比**"給 ID=2 這一行的 age 字段加1"**。
  • redo log 是循環寫的,空間固定會用完;binlog是能夠追加寫入的。追加寫是指 binlog文件寫到必定大小後會切換到下一個,並不會覆蓋之前的日誌。

3.4 update 語句的執行流程

瞭解了兩種日誌的概念,再來看看執行器和 InnoDB 引擎在執行 update 語句時的流程:

  • 執行器取 id = 2 的行數據。ID 是主鍵,引擎用樹搜索找到這一行。若是這一行所在的數據頁原本就在內存中,就直接返回給執行器;不然,須要先從磁盤讀入內存,再返回。
  • 執行器拿到引擎給的行數據,把這個值加上 1,好比原來是 N,如今就是 N+1,獲得新的一行數據,再調用引擎接口寫入這行新數據。
  • 引擎將這行新數據更新到內存中,同時將這個更新操做記錄到 redo log 裏面,此時 redo log 處於 prepare 狀態。而後告知執行器執行完成了,隨時能夠提交事務。
  • 執行器生成這個操做的 binlog,並把 binlog 寫入磁盤。
  • 執行器調用引擎的提交事務接口,引擎把剛剛寫入的 redo log 改爲提交(commit)狀態,redo log 會寫入 binlog 的文件名和位置信息來保證 binlog 和 redo log 的一致性,更新完成。

整個過程以下圖所示,其中橙色框表示是在 InnoDB 內部執行的,綠色框表示是在執行器中執行的:

update 語句的執行過程

3.5 兩階段提交

因爲 redo log 和 binlog 是兩個獨立的邏輯,若是不用兩階段提交,要麼就是先寫完 redo log 再寫 binlog,或者採用反過來的順序。咱們看看這兩種方式會有什麼問題。

仍然用前面的 update 語句來作例子。假設當前 id=2 的行,字段 age 的值是 22,再假設執行update 語句過程當中在寫完第一個日誌後,第二個日誌尚未寫完期間發生了 crash,會出現什麼狀況呢?

  1. 先寫redo log 後寫binlog。假設在redo log寫完,binlog 尚未寫完的時候,MySQL進程異常重啓。因爲咱們前面說過的,redo log 寫完以後,系統即便崩潰,仍然可以把數據恢復回來,因此恢復後這一行 age 的值是 22。

可是 binlog 沒寫完就 crash 了,這時 binlog 裏面並無記錄這個語句。所以,以後備份日誌的時候,存起來的 binlog 裏面就沒有這條語句。

等到須要用這個binlog 來恢復臨時庫的話,因爲這個語句的 binlog 丟失,這個臨時庫就會少了這一次更新,恢復出來的這一行 age 值就是 22,與原庫的值不一樣。

  1. 先寫 binlog 後寫 redo log。若是在 binlog 寫完以後 crash,因爲 redo log 還沒寫,崩潰恢復之後這個事務無效,因此 age 的值是 22。可是 binlog 裏面已經記錄了"把從 22 改爲 23" 這個日誌。因此,在以後用 binlog 來恢復的時候就多了一個事務出來,恢復出來的這一行 age 的值就是 23,與原庫的值不一樣。

因此,若是不使用"兩階段提交",數據庫的狀態就有可能和用 binlog 恢復出來的不一致。

另外:sync_binlog 這個參數建議設置成 1,表示每次事務的binlog都持久化到磁盤,這樣能夠保證 MySQL 異常重啓以後 binlog 不丟失

3.6 binlog 的應用場景

  • 主從複製 :在 Master 端開啓 binlog ,而後將 binlog 發送到各個 Slave 端, Slave 端重放 binlog 從而達到主從數據一致。

  • 數據恢復 :經過使用 mysqlbinlog 工具來恢復數據。

04 數據恢復的過程

前面說過,binlog 會記錄全部的邏輯操做,而且是採用"追加寫"的形式。若是你的DBA承諾說一個月內能夠恢復,那麼備份系統中必定會保存最近一個月的全部 binlog,同時系統會按期作整庫備份。這裏的"按期"取決於系統的重要性,能夠是一天一備,也能夠是一週一備。

當須要恢復到指定的某一秒時,好比某天下午兩點發現中午十二點有一次誤刪表,須要找回數據,那你能夠這麼作:

  • 首先,找到最近的一次全量備份,若是你運氣好,可能就是昨天晚上的一個備份,從這個備份恢復到臨時庫;
  • 而後,從備份的時間點開始,將備份的binlog依次取出來,重放到中午誤刪表以前的那個時刻。

這樣你的臨時庫就跟誤刪以前的線上庫同樣了,而後你能夠把表數據從臨時庫取出來,按須要恢復到線上庫。

看到這裏,小胖露出了目視父親的笑容。

巨人的肩膀

  • 《高性能MySQL》
  • zhihu.com/question/411272546/answer/1375199755
  • zhihu.com/question/425750274/answer/1525436152
  • time.geekbang.org/column/article/68633
  • my.oschina.net/vivotech/blog/4289724
  • hiddenpps.blog.csdn.net/article/details/108505371

05 總結

本文講解了事務日誌(redo og)的幾個方面:爲何須要 redo log?它的寫入過程、結構、存的啥以及什麼是 crash-save等等;此外還聊了 binlog 的定義、日誌格式、與 redo log 的區別、update 語句的執行流程、兩階段提交、以及 binlog 的應用場景。

好啦,以上就是狗哥關於 MySQL 日誌的總結。感謝各技術社區大佬們的付出,若是說我看得更遠,那是由於我站在大家的肩膀上。但願這篇文章對你有幫助,咱們下篇文章見~

06 送點書

若是看到這裏,喜歡這篇文章的話,請幫點個好看。微信搜索JavaFish,關注後回覆電子書送你 1000+ 本編程電子書 ,包括 C、C++、Java、Python、GO、Linux、Git、數據庫、設計模式、前端、人工智能、面試相關、數據結構與算法以及計算機基礎,詳情看下圖。回覆 1024送你一套完整的 java 視頻教程。

資源

相關文章
相關標籤/搜索