糟了,資料庫崩了,又好像沒蹦

資料庫工作筆記發表於2023-12-04


來源:waynblog

前言

2023 年某一天週末,新手程式設計師小明因為領導安排的一個活來到公司加班,小明三下五除二,按照領導要求寫了一個跑批的資料落庫任務在測試環境執行 ,突然間公司停電了,小明大驚,“糟了,MySQL 還在跑任務,會不會因為突然斷電,導致資料庫崩了”。

這時候,傍邊的同事雲淡風清的說了一句,“沒事,小明,MySQL 有一套預寫日誌機制就是應對這種情況的。你的落庫任務啟用了事務沒,啟用了的話,就等來電重新跑一下任務就行了。”

聽了同事的話,小明懸著的心放了下來。“哦哦,我啟用了事務,那我還是等週一來重新跑一遍”。

回家的公交車上,小明默默的開啟百度,搜尋 MySQL 預寫日誌 ,寫下了這篇文章 😂。

糟了,資料庫崩了,又好像沒蹦

什麼是預寫日誌機制?

一般情況下,大部分資料庫都是將表和索引儲存在磁碟檔案中。當新增資料時,資料庫系統會先寫入記憶體,然後將其寫入磁碟上的資料檔案。

那為什麼不直接寫入磁碟嘞?主要是每次新增都直接寫入磁碟效能很低,放在記憶體中,可以批次寫入磁碟以提升效能。

但有一個問題,如果資料在寫入磁碟檔案中途斷電怎麼辦?當來電恢復後,我們重啟資料庫,發現資料不一致,又該如何處理。

所以我們需要一些其他機制來避免斷電引發的資料不一致,其實 MySQL 已經考慮到了這一點,內部已經實現一套 WAL(預寫日誌)機制來避免這一點。

MySQL 設計有健壯的恢復機制,特別是使用 InnoDB 儲存引擎的情況下,它能夠在斷電後重啟而不會崩潰。InnoDB 儲存引擎使用預寫日誌(WAL)機制來確保資料的一致性和原子性。

預寫日誌機制是一種資料庫事務日誌技術,它要求在任何資料庫修改被寫入到永久儲存(也就是磁碟)之前,先將這些修改記錄到日誌中。

這樣當 MySQL 遇到意外的斷電情況時,它會在重啟後利用 Redo log 來恢復已提交但未寫入資料檔案的事務繼續寫入資料檔案,從而保證一致性,再利用 undo log 來撤銷未提交事務的需改,從而保證原子性。

MySQL 中的預寫日誌機制

糟了,資料庫崩了,又好像沒蹦

在 MySQL 中,InnoDB 儲存引擎實現了 WAL 機制。包含 Redo log buffer、Redo log、Undo Log 等,來記錄事務已提交但未寫入資料檔案的資料變更以及事務回滾後的資料還原。

為了給大家講清楚 MySQL 的預寫日誌機制,會涉及到 MySQL 架構中的以下內容,

Buffer Pool(緩衝池)

Buffer Pool (緩衝池)是 InnoDB 儲存引擎中非常重要的記憶體結構,顧名思義,緩衝池就是起到一個快取的作用,因為我們都知道 MySQL 的資料最終是儲存在磁碟中的,如果沒有這個 Buffer Pool 那麼我們每次的資料庫請求都會磁碟中查詢,這樣必然會存在 IO 操作,這肯定是無法接受的。

但是有了 Buffer Pool 就是我們第一次在查詢的時候會將查詢的結果存到 Buffer Pool 中,這樣後面再有請求的時候就會先從緩衝池中去查詢,如果沒有再去磁碟中查詢,然後在放到 Buffer Pool 中。

Redo log buffer(日誌緩衝區)

Redo log buffer 是用作資料變更記錄寫入 Redo log 檔案前的一塊記憶體區域。日誌緩衝區大小由 innodb_log_buffer_size 變數定義,預設大小為 16MB。

日誌緩衝區的內容會定期重新整理到 Redo log 檔案中,大型日誌緩衝區允許大型事務執行,而無需在事務提交之前將 Redo log 資料寫入磁碟。因此如果事務涉及的更新、插入或刪除運算元據量較大時,可以增加日誌緩衝區的大小可以節省磁碟 I/O。

MySQL 提交事務的時候,會將 Redo log buffer 中的資料寫入到 Redo log 檔案中,刷磁碟可以透過 innodb_flush_log_at_trx_commit 引數來設定

  • 值為 0 表示不刷入磁碟
  • 值為 1 表示立即刷入磁碟
  • 值為 2 表示先刷到 os cache

為了提高效能,MySQL 首先將修改操作寫入到日誌緩衝區,之後以 innodb_flush_log_at_trx_commit 引數設定落盤時機,將日誌緩衝區刷入到磁碟的 Redo log 檔案中去。

Redo Log

MySQL Redo Log 是 InnoDB 儲存引擎中的一個重要元件,它是一種磁碟基礎的資料結構,用於在崩潰重啟期間修復由已提交事務但未寫入資料檔案的資料。

在正常操作中,Redo log 記錄了由 SQL 語句執行導致的表資料變更記錄。將 Redo log buffer 中的資料持久化到磁碟中,就是將 Redo log buffer 中的資料寫入到 Redo log 磁碟檔案中。

資料在由 Redo log buffer 寫入 Redo log 時的觸發時機如下,

  • MySQL 正常關閉時觸發
  • 當 Redo log buffer 中記錄的寫入量大於 Redo log buffer 記憶體空間的一半時,會觸發落盤
  • InnoDB 的後臺執行緒每隔 1 秒,將 Redo log buffer 持久化到磁碟
  • 每次事務提交時都將快取在 redo log buffer 裡的 redo log 直接持久化到磁碟(這個策略就是由上文提高 innodb_flush_log_at_trx_commit 引數控制)

Redo log 是 WAL 機制的核心,它記錄了事務所做的所有修改。如果資料庫發生故障,可以使用 Redo 日誌來重做事務,從而確保資料的一致性。

Undo Log

Undo Log 記錄瞭如何撤銷一個事務的修改。如果需要回滾事務或在執行事務時還未提交,資料庫就發生了崩潰,這時我們就需要將未提交事務前的資料回滾回去,難道這個操作有我們自己來做嗎?顯然 MySQL 也考慮到了這一點。

MySQL 會使用 Undo log 來撤銷未提交的修改。在運算元據前,MySQL 首先將資料備份到 Undo log,然後進行資料修改。

如果出現錯誤或者使用者執行了 Rollback 語句,系統可以利用 Undo log 中的備份將資料恢復到事務操作前的狀態。

透過 Undo log 撤銷修改,從而確保資料的原子性。


結合 Buffer Pool、Redo log buffer、Redo log、Undo log 後,我們在MySQL 中更新一條資料的流程如下,

糟了,資料庫崩了,又好像沒蹦
  • 準備更新一條 SQL 語句
  • MySQL(innodb)會先去緩衝池(Buffer Pool)中去查詢這條資料,沒找到就會去磁碟中查詢,如果查詢到就會將這條資料載入到緩衝池(Buffer Pool)中
  • 在載入到 Buffer Pool 的同時,會將這條資料的原始記錄儲存到 undo 日誌檔案中
  • innodb 會在 Buffer Pool 中執行更新操作
  • 更新後的資料會記錄在 Redo log buffer 中
  • MySQL 提交事務的時候,會將 Redo log buffer 中的資料寫入到 Redo log 檔案中,刷磁碟可以透過 innodb_flush_log_at_trx_commit 引數來設定
  • MySQL 重啟的時候會將 Redo log 恢復到緩衝池中

額外知識:檢查點(Checkpoint)

檢查點是什麼?為什麼有了 Redo log、Undo log 還要引入檢查點。

明明藉助 Redo log、Undo log 我們就可以實現 MySQL 的故障恢復了。

雖然資料在寫入 Redo log 檔案後,就代表資料變更已經生效了,但是還未寫入到資料檔案,也就是還沒有完成事務的永續性。

那麼檢查點就是幫助 MySQL 實現事務的永續性。

如果說 Redo log 可以無限地增大,能夠儲存所有資料庫變更的資料,那麼在發生當機時完全可以透過 Redo log 來恢復資料庫系統的資料到當機發生前的情況。

然而現實是我們的物理磁碟檔案大小是有效的。即使達成無限了,如果資料庫執行了很久後發生當機,那麼使用 Redo log 進行恢復的時間也會非常的久。

所以在 Redo log 檔案容量是有限的情況下,還需要定期將 Redo log 寫入資料檔案完成資料的持久化,在這樣的情況下,就引入了 Checkpoint(檢查點)技術。

Checkpoint(檢查點)技術不僅僅是會同步 Redo log 寫入資料檔案,也會同步髒頁資料寫入資料檔案。

檢查點的觸發時機有兩種如下,

Sharp Checkpoint(完全檢查點)

將記憶體中所有髒頁全部寫到磁碟就是完全檢查點,比如資料庫例項關閉時。

Fuzzy Checkpoint(模糊檢查點)

將部分髒頁重新整理到磁碟,就是模糊檢查點,一般就是髒頁達到一定數量時觸發。資料庫例項執行過程產生的檢查基本上就是這種型別的檢查點。

因此其實 Checkpoint 就是指一個觸發點(時間點),當發生 Checkpoint 時,會將髒頁寫回磁碟,以確保資料的永續性和一致性。並且 Redo log、Undo log 檔案也可以重新覆寫,這樣可以保證重啟時不會因為 Redo log、Undo log 檔案太大而導致重啟時間過長。

斷電故障恢復案例

糟了,資料庫崩了,又好像沒蹦

OK,假如我們正在使用 MySQL 新增資料。在提交事務的過程中,突然發生了斷電,那麼這個資料會丟嗎?

我們結合上文MySQL 中更新一條資料的流程,來給大家分析下具體場景,

資料在寫入 Buffer Pool、Redo log buffer 中時,發生斷電

先說結論,會丟。因為資料沒有寫入 Redo log 前,MySQL 是沒辦法保證資料一致性的。但是這沒關係的,因為 MySQL 會認為本次事務是失敗的,在重啟後可以根據 Undo log 檔案將資料恢復到更新前的樣子,並不會有任何的影響。

資料在寫入 Redo log 檔案後,發生斷電

先說結論,不會丟。因為 Redo log buffer 中的資料已經被寫入到 Redo log 了,就算資料庫當機了,在下次重啟的時候 MySQL 也會將 Redo log 檔案內容恢復到 Buffer Pool 中進行重放。

參考資料

  • %E6%96%87%E4%BB%B6%E5%86%99%E6%BB%A1%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E
  • https://pdai.tech/md/db/sql-mysql/sql-mysql-execute.html

最後說兩句

預寫日誌機制是資料庫管理系統中保證資料安全性的關鍵技術。在 MySQL 中,透過 InnoDB 儲存引擎實現的 WAL 機制,即使在發生斷電等意外情況下,也能夠有效地保護資料不受損壞。這使得 MySQL 成為了一個可靠和健壯的資料庫解決方案,適用於各種需要高資料一致性和可靠性的應用場景。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027826/viewspace-2998592/,如需轉載,請註明出處,否則將追究法律責任。

相關文章