這篇文章帶給大家的內容是關於MySQL中常見的日誌問題介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
MySQL 裡有兩個日誌,分別是:重做日誌(redo log)和歸檔日誌(binlog)。 (推薦課程:MySQL教學)
#其中,binlog 可以給備庫使用,也可以儲存起來用於還原資料庫歷史資料。它是實作在 server 層的,所有引擎可以共用。 redo log 是 InnoDB 特有的日誌,用來支援 crash-safe 能力。
你一定聽過 MySQL 事務的兩階段提交,指的就是在事務提交的時候,分成 prepare 和 commit 兩個階段。
如圖 1 所示為一個事務的執行流程,你在最後三步驟可以看到,redo log 先 prepare 完成,再寫 binlog,最後才進入 redo log commit 階段。
圖1 兩階段提交示意圖
這裡,我要先跟你解釋一個誤會式的問題:這個圖不就是一個update 語句的執行流程嗎,怎麼還會呼叫commit 語句?
通常情況下,你會產生這個疑問的原因,在於把兩個「commit」的概念混淆了:
問題中的“commit 語句”,是指MySQL 語法中,用來提交一個事務的指令。一般會跟著 begin/start transaction 配對使用。
而我們圖中用到的這個“commit 步驟”,指的是事務提交過程中的一個小步驟,也是最後一步。當這個步驟執行完成後,這個事務就提交完成了。
「commit 語句」執行的時候,會包含「commit 步驟」。
而我們這個例子裡面,沒有明確地開啟事務,因此這個 update 語句本身就是一個事務,在執行完成後提交事務時,就會用到這個「commit 步驟」。
接下來,我們就一起分析一下在兩階段提交的不同時刻,MySQL 異常重啟會出現什麼現象。
如果在圖中時刻A 的地方,也就是寫入redo log 處於prepare 階段之後、寫binlog 之前,發生了崩潰(crash),由於此時binlog 還沒寫,redo log 也還沒提交,所以當機恢復的時候,這個事務會回滾。這時候,binlog 還沒寫,所以也不會傳到備庫。到這裡,我們都可以理解。
而我們理解會出現問題的地方,主要集中在時刻 B,也就是 binlog 寫完,redo log 還沒 commit 前發生 crash,那麼崩潰復原的時候 MySQL 會怎麼處理?
我們先來看看崩潰復原時的判斷規則。
1、如果redo log 裡面的事務是完整的,也就是已經有了commit 標識,則直接提交;
2、如果redo log 裡面的事務只有完整的prepare,則判斷對應的事務binlog 是否存在並完整:
a.如果是,則提交事務;
b.否則,回滾事務。
這裡,時刻 B 發生 crash 對應的就是 2(a) 的情況,崩潰復原過程中事務會被提交。
現在,我們就針對兩階段提交再繼續延展一下。
回答:一個交易的binlog 是有完整格式的:
如果碰到既有prepare、又有commit 的redo log,就直接提交;
如果碰到只有parepare、而沒有commit 的redo log,就拿著XID 去binlog 找對應的事務。
問題3:處於prepare 階段的redo log 加上完整binlog,重啟就能恢復,MySQL 為什麼要這麼設計?
回答:其實,這個問題還是跟我們在反證法中說到的資料與備份的一致性有關。在時刻 B,也就是 binlog 寫完以後 MySQL 發生崩潰,這時候 binlog 已經寫入了,之後就會被從函式庫(或是用這個 binlog 恢復出來的函式庫)使用。
所以,在主庫上也要提交這個事務。採用這個策略,主庫和備庫的資料就保證了一致性。
問題 4:如果這樣的話,為什麼還要兩階段提交?乾脆先 redo log 寫完,再寫 binlog。崩潰復原的時候,必須得兩個日誌都完整才可以。是不是一樣的邏輯?
回答:其實,兩階段提交是經典的分散式系統問題,並不是 MySQL 獨有的。
如果必須要舉一個場景,來說明這麼做的必要性的話,那就是事務的持久性問題。
對於 InnoDB 引擎來說,如果 redo log 提交完成了,交易就不能回滾(如果這還允許回滾,就可能覆寫掉別的交易的更新)。而如果 redo log 直接提交,然後 binlog 寫入的時候失敗,InnoDB 又回滾不了,資料和 binlog 日誌又不一致了。
兩階段提交就是為了給所有人一個機會,當每個人都說「我 ok」的時候,再一起提交。
問題 5:不引入兩個日誌,也就沒有兩階段提交的必要了。只用 binlog 來支援崩潰恢復,又能支援歸檔,不就可以了?
回答:我把這個問題再翻譯一下的話,是說只保留binlog,然後可以把提交流程改成這樣:... -> “數據更新到內存” -> “寫binlog ” -> “提交事務”,是不是也可以提供崩潰恢復的能力?
答案是不行。
如果說歷史原因的話,那就是 InnoDB 並不是 MySQL 的原生儲存引擎。 MySQL 的原生引擎是 MyISAM,設計之初就有沒有支援崩潰復原。
InnoDB 在加入 MySQL 引擎家族作為 MySQL 的外掛程式之前,就已經是一個提供了崩潰復原和事務支援的引擎了。
InnoDB 存取了 MySQL 後,發現既然 binlog 沒有崩潰復原的能力,那就用 InnoDB 原有的 redo log 好了。
而如果說實作上的原因的話,就有很多了。就按照問題中說的,只用 binlog 來實現崩潰復原的流程,我畫了一張示意圖,這裡就沒有 redo log 了。
圖 2 只用 binlog 支援崩潰復原
#這樣的流程下,binlog 還是無法支援崩潰復原的。我說一個不支援的點吧:binlog 沒有能力恢復「資料頁」。
如果在圖中標的位置,也就是 binlog2 寫完了,但是整個事務還沒有 commit 的時候,MySQL 發生了 crash。
重啟後,引擎內部事務 2 會回滾,然後應用 binlog2 可以補回來;但是對於事務 1 來說,系統已經認為提交完成了,不會再應用一次 binlog1。
但是,InnoDB 引擎使用的是 WAL 技術,執行事務的時候,寫完記憶體和日誌,事務就算完成了。如果之後崩潰,請依賴日誌來復原資料頁。
也就是說在圖中這個位置發生崩潰的話,事務 1 也是可能遺失了的,而且是資料頁級的遺失。此時,binlog 裡面並沒有記錄資料頁的更新細節,是補不回來的。
你如果要說,那我優化一下 binlog 的內容,讓它來記錄資料頁的變更可以嗎?可以,但這其實就是又做了一個 redo log 出來。
所以,至少現在的 binlog 能力,還不能支援崩潰復原。
問題 6:那能不能反過來,只用 redo log,不要 binlog?
回答:如果只從崩潰復原的角度來講是可以的。你可以把 binlog 關掉,這樣就沒有兩階段提交了,但係統仍然是 crash-safe 的。
但是,如果你了解業界各公司的使用場景的話,你會發現在正式的生產庫上,binlog 都是開著的。因為 binlog 有著 redo log 無法取代的功能。
一個是歸檔。 redo log 是循環寫,寫到最後是要回到開頭繼續寫的。這樣歷史日誌沒辦法保留,redo log 也就起不到歸檔的作用。
一個就是 MySQL 系統依賴 binlog。 binlog 作為 MySQL 一開始就有的功能,被用在了很多地方。其中,MySQL 系統高可用的基礎,就是 binlog 複製。
還有很多公司有異質系統(例如一些資料分析系統),這些系統就靠消費 MySQL 的 binlog 來更新自己的資料。關掉 binlog 的話,這些下游系統就沒辦法輸入了。
總之,由於現在包括 MySQL 高可用在內的許多系統機制都依賴 binlog,所以「鳩佔鵲巢」 redo log 還做不到。你看,發展生態是多麼重要。
最後,推薦你追蹤丁奇的《MySQL 實戰 45 講》專欄。在專欄裡,丁奇會幫你梳理出學習MySQL 的主線知識,例如事務、索引、鎖等,還會就開發過程中經常遇到的具體問題和你分析討論,並且幫你理解問題背後的本質。你會收穫 MySQL 核心技術詳解與原理說明和 36 個 MySQL 常見痛點問題解析。
statement 格式的 binlog,最後會有 COMMIT;
row 格式的 binlog,最後會有一個 XID event。
另外,在 MySQL 5.6.2 版本之後,也引進了 binlog-checksum 參數,用來驗證 binlog 內容的正確性。對於 binlog 日誌由於磁碟原因,可能會在日誌中間出錯的情況,MySQL 可以透過校驗 checksum 的結果來發現。所以,MySQL 還是有辦法驗證事務 binlog 的完整性的。
回答:它們有一個共同的資料字段,叫 XID。當崩潰復原的時候,會依序掃描 redo log:
以上是MySQL中常見的日誌問題介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!