1. 程式人生 > 其它 >MySQL學習(九)小結(轉載)

MySQL學習(九)小結(轉載)

前言

文章總結於<<MySQL45講>> ,非原創.
主要的內容的是關於 redo logbin log

問題

追問1:MySQL怎麼知道binlog是完整的?

回答:一個事務的binlog是有完整格式的:

statement格式的binlog,最後會有COMMIT;
row格式的binlog,最後會有一個XID event。
另外,在MySQL 5.6.2版本以後,還引入了binlog-checksum引數,用來驗證binlog內容的正確性。對於binlog日誌由於磁碟原因,可能會在日誌中間出錯的情況,MySQL可以通過校驗checksum的結果來發現。所以,MySQL還是有辦法驗證事務binlog的完整性的。

追問2:redo log 和 binlog是怎麼關聯起來的?

回答:它們有一個共同的資料欄位,叫XID。崩潰恢復的時候,會按順序掃描redo log:

如果碰到既有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還做不到。你看,發展生態是多麼重要。

追問7:redo log一般設定多大?

回答:redo log太小的話,會導致很快就被寫滿,然後不得不強行刷redo log,這樣WAL機制的能力就發揮不出來了。

所以,如果是現在常見的幾個TB的磁碟的話,就不要太小氣了,直接將redo log設定為4個檔案、每個檔案1GB吧。

追問8:正常執行中的例項,資料寫入後的最終落盤,是從redo log更新過來的還是從buffer pool更新過來的呢?
回答:這個問題其實問得非常好。這裡涉及到了,“redo log裡面到底是什麼”的問題。

實際上,redo log並沒有記錄資料頁的完整資料,所以它並沒有能力自己去更新磁碟資料頁,也就不存在“資料最終落盤,是由redo log更新過去”的情況。

如果是正常執行的例項的話,資料頁被修改以後,跟磁碟的資料頁不一致,稱為髒頁。最終資料落盤,就是把記憶體中的資料頁寫盤。這個過程,甚至與redo log毫無關係。

在崩潰恢復場景中,InnoDB如果判斷到一個數據頁可能在崩潰恢復的時候丟失了更新,就會將它讀到記憶體,然後讓redo log更新記憶體內容。更新完成後,記憶體頁變成髒頁,就回到了第一種情況的狀態。

追問9:redo log buffer是什麼?是先修改記憶體,還是先寫redo log檔案?

回答:這兩個問題可以一起回答。

在一個事務的更新過程中,日誌是要寫多次的。比如下面這個事務:

begin;
insert into t1 ...
insert into t2 ...
commit;

這個事務要往兩個表中插入記錄,插入資料的過程中,生成的日誌都得先儲存起來,但又不能在還沒commit的時候就直接寫到redo log檔案裡。

所以,redo log buffer就是一塊記憶體,用來先存redo日誌的。也就是說,在執行第一個insert的時候,資料的記憶體被修改了,redo log buffer也寫入了日誌。

但是,真正把日誌寫到redo log檔案(檔名是 ib_logfile+數字),是在執行commit語句的時候做的。

(這裡說的是事務執行過程中不會“主動去刷盤”,以減少不必要的IO消耗。但是可能會出現“被動寫入磁碟”,比如記憶體不夠、其他事務提交等情況。

單獨執行一個更新語句的時候,InnoDB會自己啟動一個事務,在語句執行完成的時候提交。過程跟上面是一樣的,只不過是“壓縮”到了一個語句裡面完成。

問題1:執行一個update語句以後,我再去執行hexdump命令直接檢視ibd檔案內容,為什麼沒有看到資料有改變呢?

回答:這可能是因為WAL機制的原因。update語句執行完成後,InnoDB只保證寫完了redo log、記憶體,可能還沒來得及將資料寫到磁碟。

問題2:為什麼binlog cache是每個執行緒自己維護的,而redo log buffer是全域性共用的?

回答:MySQL這麼設計的主要原因是,binlog是不能“被打斷的”。一個事務的binlog必須連續寫,因此要整個事務完成後,再一起寫到檔案裡。

而redo log並沒有這個要求,中間有生成的日誌可以寫到redo log buffer中。redo log buffer中的內容還能“搭便車”,其他事務提交的時候可以被一起寫到磁碟中。

問題3:事務執行期間,還沒到提交階段,如果發生crash的話,redo log肯定丟了,這會不會導致主備不一致呢?

回答:不會。因為這時候binlog 也還在binlog cache裡,沒發給備庫。crash以後redo log和binlog都沒有了,從業務角度看這個事務也沒有提交,所以資料是一致的。

問題4:如果binlog寫完盤以後發生crash,這時候還沒給客戶端答覆就重啟了。等客戶端再重連進來,發現事務已經提交成功了,這是不是bug?

回答:不是。

你可以設想一下更極端的情況,整個事務都提交成功了,redo log commit完成了,備庫也收到binlog並執行了。但是主庫和客戶端網路斷開了,導致事務成功的包返回不回去,這時候客戶端也會收到“網路斷開”的異常。這種也只能算是事務成功的,不能認為是bug。

實際上資料庫的crash-safe保證的是:

如果客戶端收到事務成功的訊息,事務就一定持久化了;

如果客戶端收到事務失敗(比如主鍵衝突、回滾等)的訊息,事務就一定失敗了;

如果客戶端收到“執行異常”的訊息,應用需要重連後通過查詢當前狀態來繼續後續的邏輯。此時資料庫只需要保證內部(資料和日誌之間,主庫和備庫之間)一致就可以了。

參考資料