你們都清楚,日誌是 MySQL 數據庫的重要組成部分,記錄着數據庫運行期間各類狀態信息。MySQL 日誌主要包括「錯誤日誌」、「查詢日誌」、「慢查詢日誌」、「二進制日誌(binlog)」 和 事務日誌(redo log、undo log)幾大類。javascript
其中,「二進制日記」和「事務日記」尤其重要,一直被人重視、深刻研究;但是事實很殘忍,重視或者說大多數人通常都是瞭解個表面,真正懂得人並很少。真想攻破這兩塊日記必須下血本,並且還不必定能攻破。可是沒關係,爲了讓大家省下血本還能順利攻破這兩塊日記,我連續研究幾周 MySQL日記,最終肝出了這篇文章。java
基礎不牢地動山搖,仍是常規套路,先把必要知識普及/溫習一遍,當後續文章出現疑慮反過來看下這些概念字典,說不定能柳暗花明又一村呢?mysql
寫了又寫,想了又想,糾結了很久,這部分知識確實有點多,最後仍是決定將這些必要概念字典單獨分出一個文章,後續打算用截圖方式引入各個章節中,建議遇到不懂名詞查閱一下字典。面試
Binlog 是邏輯日記,用於記錄數據庫執行的寫入操做(查詢不記錄)信息,Server 層記錄和引擎層無關,而且是以追加方式進行寫入,能夠經過參數 max_binlog_size 設置每一個 Binlog 文件的大小,文件大小達到設定值時會生成新的文件來保存日記。sql
在實際應用中,主要用在兩個場景:主從複製和數據恢復數據庫
Binlog 大體記錄過程是先寫 Binlog Buffer,而後經過刷盤時機,控制刷入 OS Buffer,控制 fsync() 進行寫入 Binlog File 日記磁盤的過程。緩存
對於 Binlog,MySQL 是經過參數 sync_binlog 參數來控制刷盤時機,取值是 0、1 和 N 三種值。0 表示由系統自行判斷什麼時候調用 sync() 寫入磁盤;1 表示每次事務 commit 都要調用 fsync() 寫入磁盤;N 表示每 N 個事務,纔會調用 fsync() 寫入磁盤。安全
MySQL 5.7.7 版本以前默認格式是 STATEMENT,版本以後默認是 ROW,能夠經過參數 binlog-format 指定。工具
Undo log是 邏輯日記 、回滾日記。好比一條修改 +3 的邏輯語句,Undo log 會記錄對應一條 -3 的邏輯日記,一條插入語句則會記錄一條刪除語句,這樣發生錯誤時,根據執行 Undo log 就能夠回滾到事務以前的數據狀態。性能
刷盤過程及時機相似於 Binlog 和 Redo,能夠參考 Redo log 刷盤時機章節給出的圖片,已經體現出來了。
Undo log 日記內容不是不少,重點是回滾和多版本控制 MVCC那塊。此外,我記得印象筆記深入的是長事務會致使日記過多,這個日記就是 Undo log。由於長事務存在,致使須要保存不少視圖快照,其實這裏就是涉及到Undo log 什麼時候刪除和生成的問題,當時糾結很久,其實很簡單。生成是事務開始後寫 Redo log 以前生成,當沒有事務須要用到 Undo log 時就會被刪除。舉個例子,若是事務 A 一直存活,那麼事務 A 以後產生的事務 B、C...等等就算提交了,也不會被刪除,由於事務 A 須要用到 B、C... 事務去找 A 的版本。因此避免長事務能夠減小Undo log 日記量,固然還能夠提升性能。
Redo log 是重作日記,屬於InnoDB引擎的日記。是物理日記,日記記錄的內容的是數據<typo id="typo-1882" data-origin="頁" ignoretag="true">頁</typo>的更改,這個頁 「作了什麼改動」。如:add xx記錄 to Page1,向數據頁Page1增長一個記錄。
更新內存後引擎<typo id="typo-2178" data-origin="層" ignoretag="true">層</typo>寫 Redo log 將狀態改爲 prepare 爲預提交第一階段,Server 層寫 Binlog,將狀態改爲 commit爲提交第二階段。兩階段提交能夠確保 Binlog 和 Redo log 數據一致性。
MySQL的處理過程以下
只有在 redo log 狀態爲 prepare 時,纔會去檢查 binlog 是否存在,不然只校驗 redo log 是不是 commit 就能夠啦。怎麼檢查 binlog:一個完整事務 binlog 結尾有固定的格式。
Undo log 的刷盤時機和 Redo log 差很少,可是對於 Undo log 我沒找到對應的刷盤參數設計,因此<typo id="typo-2666" data-origin="不在" ignoretag="true">不在</typo>提。Redo log 每次先寫入 Redo Log Buffer 中,而後經過刷盤時機控制刷入 OS Buffer 時間和刷入日記磁盤的時間。
在 Undo Log 中,MySQL 是經過參數innodb_flush_log_at_trx_commit來控制刷盤時機,取值是 0、1 和 2三種值。0 表示事務提交後,每秒寫入 OS Buffer 並調用 fsync() 寫入日記磁盤中;1 表示每次事務提交會寫入OS Buffer 並調用 fsync() 將日記寫入日記磁盤中。2 表示事務每次提交寫入到 OS Buffer,每秒調用 fsync() 寫入日記磁盤。可見參數爲 1 是最安全的,同時也是默認值。
上圖是日記磁盤的 Redo log 環形設計圖(從頭寫,寫到結束又從頭開始寫~循環)。write pos 和 check point 是兩個指針,write pos指針指向當前日記文件寫入的位置,check point 指針指向當前要擦除的開始位置。圖中綠色部分是能夠寫入 Redo log 地方,每次寫入,write pos 指針會順時針推動,固然基本不會與 check point 指針重合,由於 MySQL 有這種機制去實現,每次觸發檢查點 checkpoint,check point 會指針向前推動,這個過程就是須要進行刷日記和數據磁盤,記錄相應的 LSN,引出難點 LSN。
啥時候會觸發檢查點 checkpoint
「Checkpoint 發生的時間、條件及髒頁的選擇等都很是複雜。而 Checkpoint 所作的事情無外乎是將緩衝池中的髒頁刷回到磁盤,不一樣之處在於每次刷新多少頁到磁盤,每次從哪裏取髒頁,以及什麼時間觸發 Checkpoint。這些本文不會去研究」。
LSN 這個概念,比較複雜。LSN 稱爲日誌的邏輯序列號(log sequence number),在 innodb 存儲引擎中,lsn佔用 8 個字節。LSN 的值會隨着日誌的寫入而逐漸增大。「能夠簡單理解SLN就是記錄從開始到如今已經產生了多少字節的Redo log值」。
存儲方式兩個指針又是經過 LSN 計算獲得指向位置,由於 LSN 記錄的是文件的大小字節,當超過文件大小時,須要用取模計算出這兩個指針位置,取模使得寫入就會從頭開始寫,這樣使得兩個指針在一個文件中,一直落在循環位置,你追我趕的過程。這就是 Redo log 環形邏輯思想設計實現。
上面提到LSN比較複雜,是由於它有不少個值,輸入命令 show engine innodb status; ,能夠看到四個的 lsn 記錄
爲了方便識別,我都爲它們從新命名,以下所示。名詞記不住,後面沒法繼續深刻
通常關係爲:redo log buffer lsn >= redo log file lsn,若是刷盤時機爲1,則redo log buffer lsn = redo log file lsn。
通常關係爲data buffer lsn > data disk lsn,若是已經刷入數據磁盤,則data buffer lsn = data disk lsn。
後面提到檢查點刷盤,數據刷盤和日記刷盤(若是有日記刷盤:則說明我假設的日記刷盤的時機設置值不爲1,爲1 是同步的,即始終 redo log buffer lsn = redo log file lsn,不會由檢查點觸發刷日記磁盤)。
都說 Redo log 是環形記錄,那麼怎麼記錄的?下面結合 LSN 給出記錄過程虛構圖,能夠對比 Redo log 存儲方式圖
❝相關知識:日記磁盤 + redo log file lsn + checkpoint lsn + 雙指針(write pos、check point)
❞
1-8 按時間順序發生。1 點是假設最初的狀態;二、3 點寫日記磁盤;4 點是觸發了檢查點 checkpoint,進行刷盤,「checkpoint lsn=1開始」,刷盤結束並更新「checkpoint lsn=512」。在 5 點、6 點已經刷過了一循環內存、二循環內存,「從頭開始寫入 log,兩個指針指向回到了頭部」。第 7 點也是一個觸發 checkpoint 的過程。9 點是假設沒有更新,最後達到平衡的結果,即內存中數據頁和日記都完成了刷盤。
整個流程:
在某些狀況下,觸發 checkpoint,觸發數據頁和日誌頁刷盤,此時將內存中的髒數據---數據髒頁和日誌髒數據"分別刷到數據磁盤和日記磁盤中,並且二者刷盤速度不同。checkpoint 會保護機制,當數據刷盤速度超過日誌刷盤時,將會暫時中止數據刷盤,等待日誌刷盤進度超過數據刷盤。
刷盤時,對於數據磁盤,所有都是在內存中,此時每次刷一個數據頁到內存更新數據頁也更新了「data disk lsn」爲 「data buffer lsn」(在更新內存數據頁時,會更新data buffer lsn)「。」
對於日記磁盤,除了要記錄 checkpoint lsn 的值爲檢查點 checkpoint的值(必須在結束時直接記錄一個值,速度很快),這裏是針對日記刷盤時機不是1(1是同步緩存刷日記刷盤)時,而且日記還沒刷到日記磁盤須要觸發將緩存中日記提早刷到日記磁盤中,此時會將redo buffer log 刷到 redo log file 中也更新了 redo log file lsn爲redo log buffer lsn 。
模擬檢查點觸發先後,整個流程變化,一個數據頁和日記,「數據變化及lsn從179-180的變化圖(刷盤時機不爲1)」
Redo log 容災恢復過程和 LSN 的知識,再次細化 Redo log 恢復過程
重啓 innodb 時,Redo log 完不完整,採用 Redo log 相關知識。用 Redo log 恢復,啓動數據庫時,InnoDB 會掃描數據磁盤的數據頁 data disk lsn 和日誌磁盤中的 checkpoint lsn。二者相等則從 checkpoint lsn 點開始恢復,恢復過程是利用 redo log 到 buffer pool,直到 checkpoint lsn 等於 redo log file lsn,則恢復完成。
若是 checkpoint lsn 小於 data disk lsn,說明在檢查點觸發後還沒結束刷盤時數據庫宕機了。由於 checkpoint lsn 最新值是在數據刷盤結束後才記錄的,檢查點以後有一部分數據已經刷入數據磁盤,這個時候數據磁盤已經寫入部分的部分恢復將不會重作,直接跳到沒有恢復的 lsn 值開始恢復。
爲啥本文我會提到 ChangeBuffer 呢,其實不少時候會將 ChangeBuffer 和 Redo log 搞混,二者都是巧用內存,減小磁盤 IO,爲了避免弄混我以爲有必要專門對這個進行一個講解。
下面是我對 ChangeBuffer 的簡單介紹
也就是說對於更新的操做,若是用到了 ChangeBuffer,更新的數據所在的數據頁若是不在內存中,將不用去數據磁盤將數據頁讀到內存,而是將這一次操做記錄在 ChangeBuffer 中,「ChangeBuffer 主要節省的則是隨機讀磁盤的 IO 消耗」,下次讀取查詢等讀取數據頁時用上 ChangeBuffer 中的記錄便可。其實也是一種巧用內存的思想。
Redo log 主要節省的是隨機寫磁盤的 IO 消耗(轉成順序寫),而 ChangeBuffer 主要節省的則是隨機讀磁盤的 IO 消耗
這句話怎麼理解,看下面:
Redo log 與 ChangeBuffer (含磁盤持久化) 這 2 個機制,不一樣之處在於優化了整個變動流程的不一樣階段。
先不考慮 Redo log、ChangeBuffer 機制,簡化抽象一個更新 (insert、update、delete) 流程:
其中,流程中的步驟 1 涉及隨機讀磁盤 IO;步驟 3 涉及隨機寫磁盤 IO;恰好對應 ChangeBuffer 和 Redo log。
對那句話的理解答案:
Redo log 機制,爲了保證 crash-safe,一直都會用到。有無用到 ChangeBuffer 機制,對於 redo log 這步的區別在於—— 用到了 ChangeBuffer 機制時,在 Redo log 中記錄的本次變動,是記錄 new change buffer item 相關的信息,而不是直接的記錄物理頁的變動。在咱們 mysql innodb 中, ChangeBuffer 機制不是一直會被應用到,僅當待操做的數據頁當前不在內存中,須要先讀磁盤加載數據頁時,ChangeBuffer 纔有用武之地。
除了訪問這個數據頁會觸發 merge 外,系統有後臺線程會按期 merge。在數據庫正常關閉(shutdown)的過程當中,也會執行 merge 操做。
merge 過程作三步
前面分別講的是 Binlog、Undo log 和 Redo log,下面將他們都串聯起來,在一些流程體現所有日記。
一樣,以一些最經典的更新語句例子展開說明。
測試語句:插入語句+查詢語句,a字段是普通索引
<pre language="javascript" code_block="true">一、insert into ta(a,b) values(2,5),(7, 5)
二、select * from t where a in (2, 7)
</pre>
假設原來的數據以下圖,數據頁 page1 在內存中,page2 不在。插入的數據 (2,5) 落在 page1,數據 (7,5) 落在page2 中。
先不考慮全部日記及 ChangeBuffer 機制,簡化抽象一個更新 insert 流程
過程是 兩階段提交-----日記刷盤------數據刷盤(涉及 Redo log lsn 和 ChangeBuffer 的內容)
緊接着上文,圖片可上下參考,假設如今執行查詢語句 select * from t where a in (2, 7) ,這次查詢索引 a=7 所在的數據頁不在內存中,而且上一步更新已經在 change buffer 中有記錄,將會觸發 merge 過程
至於 changebuffer 被應用後是刪除仍是標記,還有 redo 中原有的記錄 changebuffer 的改動怎麼調整是刪除仍是修改爲數據頁的改動這裏下面的圖是按照本身的想法描述出來,若有誤望留言指正。
[圖片上傳失敗...(image-e98fa5-1603353858527)]
數據刷盤 flush 的有四種狀況
數據刷盤也表明着 Redo log 檢查點 checkpoint 觸發,較爲複雜。
假設數據刷盤 flush 的四種狀況發生了一種,那麼聯繫上文的過程將以下
[圖片上傳失敗...(image-c2d38-1603353858527)]
流程中間某個環節數據庫宕機後,恢復具體過程,這些留在內心了,沒往上去寫,讀者能夠自行思考,不難。
整個文章講了 Binlog、Undo log 和 Redo log,隨帶一提 ChangeBuffer,對此,講到這裏,基本上要把我要講的已經講完,內容挺多,有耐心能夠慢慢啃!
思考環節,下面留下兩個問題,歡迎你們留言解答
一、爲啥 Binlog 沒有 crash-safe 功能?
二、保證 crash-safe 爲啥要用兩個日記,不能用一個日記嗎(Redo log 或 Binglog)?
歡迎關注公衆號:【Java鬥帝】
一、歡迎你們關注,持續推出乾貨~二、後臺回覆666,領取私人整理的1000道互聯網面試題