我有一個老朋友,我們叫他熊貓。發際線及將觸碰到后腦勺,大框金絲眼鏡也掩蓋不住那黝黑的眼圈,顯得格外的“程序員”;穿着也非常不拘一格,上半身是襯衣西服,下半身是牛仔褲配拖鞋~
我和熊貓的感情很好,畢業后他去了上海而我開始北漂,但每次過節回老家我倆都會和朋友們一起吃飯,這次回家過年也不例外。這次,我們朋友幾個去了棗庄出名的“好再來土菜館”,點了特色的棗庄辣子雞,超大盤那種。
這次在飯桌上,我們聊到了疫情期間我們幾個積極參加各大廠免費面試的一些有趣場景。熊貓說在面試一家數據存儲的大廠時,深挖了一個MySQL問題,redo log
和 binlog
,很有意思。另外,MySQL 系列面試題和答案全部整理好了,微信搜索Java技術棧,在后台發送:面試,可以在線閱讀。
面試官也很客氣,總有種莫名的親切感。說着,翹起二郎腿喊道:“老板,再來一箱青島”!我們幾個都知道,熊貓又要開始回放了~~
以下是熊貓和面試官馬經理的對話。
熊貓:馬…小馬哥好!
面試官:額…你好,小李啊,看你簡歷寫着精通MySQL事務、日志原理,你認為精通應該是啥水平呢?
熊貓(老臉一紅):emmm…精通就是,比面試官知道的多一點唄。。
面試官:(嗯。。。。是個老實人,我喜歡!)
面試官:那你先給我講講MySQL里有哪些比較重要的日志吧?
熊貓:(我只看了redo log、binlog面試題,咋整,多說會不會給自己挖坑?記得魯迅大爺說過:別啥JB都說,最后坑自己)
熊貓:嗯。。其實MySQL中的日志有很多,但日常接觸最多的是重做日志(redo log)、歸檔日志(binlog)這兩種,當然還有回滾日志(undo log)等等,我接觸的少一些。
MySQL日志主要包括六種:
- 重做日志(redo log)
- 回滾日志(undo log)
- 歸檔日志(binlog)
- 錯誤日志(errorlog)
- 慢查詢日志(slow query log)
- 一般查詢日志(general log)
- 中繼日志(relay log)
面試官:好,那你先說一下你對 redo log
日志的理解吧。
熊貓:記得小時候看《武林外傳》,呂秀才櫃台下面有一個小黑板,當時不知道是干啥的,后來發現是專門用來記錄客人的賒賬記錄。如果賒賬的人不多,那么他可以把顧客名和賬目寫在板上。但如果賒賬的人多了,小黑板沒地兒了,這個時秀才一定還有一個專門記錄賒賬的賬本。如果有人要賒賬或者還賬的話,秀才一般有兩種做法:
- 一種做法是直接把賬本翻出來,把這次賒的賬加上去或者扣除掉;
- 另一種做法是先在小黑板上記下這次的賬,等打烊以后再把賬本翻出來核算。
在生意火爆時,秀才肯定會選擇后者,因為直接記賬本太麻煩了。得先翻出賒賬人“老錢”那條記錄,賬本密密麻麻幾十頁,找到后再拿出算盤計算,最后更新到賬本上。
想想都麻煩。相比之下,還是先在小黑板上記一下方便。你想想,如果秀才沒有小黑板的幫助,每次記賬都得翻賬本,效率是不是低得讓人難以忍受?還有時間泡小郭?想無雙?
同樣,在 MySQL 里也有這個問題,如果每一次的更新操作都需要寫進磁盤,然后磁盤也要找到對應的那條記錄,然后再更新,整個過程 IO 成本、查找成本都很高。為了解決這個問題,MySQL 的設計者就用了類似秀才記小黑板的思路來提升更新效率。
而小黑板和賬本配合的過程,其實就是 MySQL 里經常說到的 WAL 技術
。
面試官:小伙子你這思路很奇特呀!那你再詳細跟我說一下,啥是WAL技術?
熊貓:(小馬哥對我有意思啊!)
WAL 的全稱是 Write-Ahead Logging,它的關鍵點就是先寫日志,再寫磁盤,也就是先寫小黑板,等不忙的時候再寫賬本。
具體來說,當有一條update語句要執行的時候,InnoDB 引擎就會先把記錄寫到 redo log(小黑板)里面,並更新內存,這個時候更新就算完成了。
同時,InnoDB引擎會在適當的時候,將這個操作記錄更新到磁盤里面,而這個更新往往是在系統比較空閑的時候做,這就像打烊以后秀才做的事。如果今天賒賬的不多,掌櫃可以等打烊后再整理。
但如果某天賒賬的特別多,小黑板寫滿了咋辦?這個時候秀才只好叫無雙幫忙干自己的活兒,抓緊把小黑板中的一部分賒賬記錄更新到賬本中,然后把這些記錄從小黑板上擦掉,為記新賬騰出空間。
與此類似,InnoDB 的 redo log 是固定大小的,比如可以配置為一組 4 個文件,每個文件的大小是 100MB,那么這塊“小黑板”總共就可以記錄 400MB 的操作記錄。從頭開始寫,寫到末尾就又回到開頭循環寫,如下面這個圖所示。
- write position 是當前記錄的位置,一邊寫一邊后移,寫到第 3 號文件末尾后就回到 0 號文件開頭。
- checkpoint 是當前要擦除的位置,也是往后推移並且循環的,擦除記錄前要把記錄更新到數據文件。
write position 和 checkpoint 之間的是“小黑板”上還空着的部分,可以用來記錄新的操作。
如果 write pos 追上 checkpoint,表示“小黑板”滿了,這時候不能再執行新的更新,得停下來先擦掉一些記錄,把 checkpoint 推進一下。
有了 redo log,InnoDB 就可以保證即使數據庫發生異常重啟,之前提交的記錄都不會丟失,這個能力稱為 crash-safe
。
crash-safe:
可以對照前面賒賬記錄的例子。只要賒賬記錄記在了小黑板上或寫在了賬本上,即使秀才突然被老邢抓走幾天,回來后依然可以通過賬本和小黑板上的數據明確賒賬賬目。就是維護數據的持久性。
本質上說,crash-safe 就是落盤處理,將數據存儲到了磁盤上,斷電重啟也不會丟失。
面試官:不錯,你這理解雖說聽的我一愣一愣,但是話糙理不糙,確實說出了redo log的原理。那你再說說對binlog日志的理解吧。
熊貓:嘿嘿,謝謝馬經理誇獎。MySQL 其實是分為 server層 和 引擎層兩部分。
- Server 層:它主要做的是 MySQL 功能層面的事情;
- 引擎層:負責存儲相關的具體事宜。
上面我們聊到的“小黑板” redo log 是 InnoDB 引擎特有的日志
,而 Server 層也有自己的日志,稱為binlog(歸檔日志),其實就是用來恢復數據用的。
面試官:那MySQL為啥要有redo log 和 binlog兩個日志呢?只留一個不香么?
熊貓:因為最開始 MySQL 里並沒有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒有 crash-safe 的能力,而 binlog 日志只用於歸檔。
InnoDB 是另一個公司以插件形式引入 MySQL 的。我們知道,只依靠 binlog 是沒有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系統——也就是 redo log 來實現 crash-safe 能力。
面試官:那這兩個日志主要有哪些區別?
熊貓:emmm…主要有幾下幾種區別:
- redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎共用。
- redo log 是物理日志,記錄的是“在某個數據頁上做了什么修改”;binlog 是邏輯日志,記錄的是這個語句的原始邏輯,比如“給 ID=1 這一行的 c 字段加 1 ”。
- redo log 是循環寫的,空間固定會用完然后復寫;binlog 是可以追加寫入的。“追加寫”是指 binlog 文件寫到一定大小后會切換到下一個,並不會覆蓋以前的日志。
面試官:好,基於你上面說的,那比如下面這條SQL,你來描述一下在MySQL內部的執行流程吧
update T set money = money + 500 where username = '陳哈哈';
熊貓:
- (開始,原始數據接入)執行器先找引擎取 username = ‘陳哈哈’ 這一行。如果 username = ‘陳哈哈’ 這一行所在的數據頁本來就在內存中,就直接返回給執行器;否則,需要先從磁盤讀入內存,然后再返回。
- (數據修改)執行器拿到引擎給的行數據,把 money 這字段的值加上 500,比如原來是 N,現在就是 N+500,得到新的一行數據,再調用引擎接口寫入這行新數據。
- (數據提交)提交操作,由於存儲引擎層與server層之間采用的是內部XA(保證兩個事務的一致性,這里主要保證redo log和binlog的原子性),所以提交分為prepare階段與commit階段,也就是我們說的
兩階段提交
。 - (寫redo log)引擎將這行新數據更新到內存中,同時將這個更新操作記錄到 redo log 里面(寫到內存或直接落盤),到這里, redo log 處於 prepare 狀態。
- (寫binlog)然后告知執行器執行完成了,隨時可以提交事務。執行器生成這個操作的 binlog,並把 binlog 同步到磁盤。
- (數據更新到磁盤或內存,結束)執行器調用引擎的提交事務接口執行修改操作,需要將在二級索引上做的修改,寫入到change buffer page,等到下次有其他sql需要讀取該二級索引時,再去與二級索引做merge,引擎把剛剛寫入的 redo log 標記上(commit)狀態,實際上是加上了一個與binlog對應的XID,使兩個日志邏輯保持一致,到此結束,更新流程閉環。
面試官:那為啥必須要分成prepare和commit兩個階段進行提交呢?一塊兒提交他不爽么。
熊貓:我舉個現實生活中的栗子吧,一個完整的交易過程我認為應該這樣:
比如你來我的小超市里買一瓶可樂:
- 小馬哥:老板給我來瓶可樂!透心涼心飛揚的那個。
- 我:??
- 機器掃一下可樂,告訴小馬哥這瓶可樂2塊5,不能白嫖,讓他給錢(記錄 redo log,事務處於prepare狀態)
- 收錢放入錢箱(記錄 binlog,
事務實際是否完成的根本依據
,處於待標記commit階段) - 然后讓你把可樂拿走(redo log 狀態標為 commit,表示該事務邏輯閉環)。
到這里,代表一筆交易結束
。 - 並告訴小馬哥,透心涼心飛揚那個是雪碧,你個憨X~
- 等算賬前再把這一天賣東西的交易信息一起同步到數據庫。
可見,如果收錢之前(prepare階段,步驟3)交易被打斷,回過頭來處理此次交易,發現只有記了小黑板但沒有收錢,則交易失敗,刪掉小黑板上的記錄(回滾);
如果收了錢后(commit階段 或 待commit階段,步驟4 || 5)交易被打斷,然后回過頭發現系統上有記錄(prepare)而且錢箱有本次收入(bin log),則說明本次交易有效,補充修改commit狀態,更新到庫存中。
以上是人話,咱們再來看看MySQL層面的專業解釋:
這里我們用反證法來進行解釋為何需要兩階段提交。由於 redo log 和 binlog 是兩個獨立的邏輯,如果不用兩階段提交,要么就是先寫完 redo log 再寫 binlog,或者采用反過來的順序。我們看看這兩種方式會有什么問題。
仍然用前面的 update 語句來做例子。假設當前 username = ‘陳哈哈’ 的行,賬戶余額字段 money 的值是 100,再假設執行 update 語句過程中在寫完第一個日志后,第二個日志還沒有寫完期間發生了 crash(異常宕機),會出現什么情況呢?
依舊以這條SQL為例:
update T set money = 0 + 500 where username = '陳哈哈';
1、先寫 redo log 后寫 binlog。
假設在 redo log 寫完,binlog 還沒有寫完的時候,MySQL 進程異常重啟。由於我們前面說過的,redo log 寫完之后,系統即使崩潰,仍然能夠把數據恢復回來,所以恢復后這一行 money 的值是 money + 500。
但是由於 binlog 沒寫完就 crash 了,這時候 binlog 里面就沒有記錄這個語句。因此,之后備份日志的時候,存起來的 binlog 里面就沒有這條語句。
然后你會發現,如果需要用這個 binlog 來恢復臨時庫的話,由於這個語句的 binlog 丟失,這個臨時庫就會少了這一次更新,恢復出來的這一行 money 的值就是 0,與原庫的值不同。
2、先寫 binlog 后寫 redo log。
如果在 binlog 寫完之后 crash,由於 redo log 還沒寫,崩潰恢復以后這個事務無效,用戶余額 money 的值應當是 0。
但是 binlog 里面已經記錄了“把 money 從 0 改成 500 這個日志。所以,在之后用 binlog 來恢復的時候就多了一個事務出來,恢復出來的這一行 money 的值就是 500,與原庫的值不同。
可以看到,如果不使用“兩階段提交”,那么數據庫的狀態就有可能和用它的日志恢復出來的庫的狀態不一致。
簡單說,redo log 和 binlog 都可以用於表示事務的提交狀態,而兩階段提交就是讓這兩個狀態保持邏輯上的一致。
日志落盤
保證事務成功,日志必須落盤,這樣,數據庫crash后,就不會丟失某個事務的數據了
- innodb_flush_log_at_trx_commit 這個參數設置成 1 的時候,表示每次事務的 redo log 都直接持久化到磁盤。這樣可以保證 MySQL 異常重啟之后數據不丟失。
- sync_binlog 這個參數設置成 1 的時候,表示每次事務的 binlog 都持久化到磁盤。這樣可以保證 MySQL 異常重啟之后 binlog 不丟失。
面試官:(老臉一紅)
面試官:小李,坦白說我也很久沒搞技術了,但我覺得你確實很懂這塊兒,為什么?因為你把我都給我干困了。。我很欣賞你吹…講故事的能力,你,你懂我意思吧。
面試官:小李啊,你等我一會兒。(馬經理提着褲子走出了辦公室)
熊貓:(尿遁了??不應該啊,我這波操作難道還不香么?難道,被識破了??-_-’’|)
五分鍾后HR來了。。
HR:小李,我就說你行吧!!領導很看好你,說你表達思路奇特,很符合這個崗位,並給你點了個贊,讓我通知你下周來入職吧。
熊貓:好的好的,看來現在開發對表達能力要求還挺高啊~~
HR:??兄弟不是應聘產品么?
熊貓:😀😀🙃🙃你跟我倆擱這兒扯犢子呢?我應聘的軟件開發工程師大哥?
HR:(嗯,看來果然是我打錯面試電話了。。冷靜冷靜,小問題)
HR:好的,那今天就先這樣,回去等通知吧😺😺 還有啥問題要問我么?
熊貓:。。。。。
總結
好了,今天咱們了解了 MySQL 里面最重要的兩個日志,即物理日志 redo log 和邏輯日志 binlog。另外,MySQL 系列面試題和答案全部整理好了,微信搜索Java技術棧,在后台發送:面試,可以在線閱讀。
為該講的內容總結了幾個問題, 大家復習的時候可以先嘗試回答這些問題檢查自己的掌握程度。
- redo log的概念是什么? 為什么會存在.
- 什么是WAL(write-ahead log)機制, 好處是什么.
- redo log 為什么可以保證crash safe機制.
- binlog的概念是什么, 起到什么作用, 可以做crash safe嗎?
- binlog和redolog的不同點有哪些?
- 物理一致性和邏輯一致性各應該怎么理解?
- 執行器和innoDB在執行update語句時候的流程是什么樣的?
- 如果數據庫誤操作, 如何執行數據恢復?
- 什么是兩階段提交, 為什么需要兩階段提交, 兩階段提交怎么保證數據庫中兩份日志間的邏輯一致性(什么叫邏輯一致性)?
- 如果不是兩階段提交, 先寫redo log和先寫bin log兩種情況各會遇到什么問題?
原文鏈接:https://blog.csdn.net/qq_39390545/article/details/115214802
版權聲明:本文為CSDN博主「_陳哈哈」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.別在再滿屏的 if/ else 了,試試策略模式,真香!!
3.卧槽!Java 中的 xx ≠ null 是什么新語法?
4.Spring Boot 2.5 重磅發布,黑暗模式太炸了!
覺得不錯,別忘了隨手點贊+轉發哦!