久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

MySQL的crash

188次閱讀
沒有評論

共計 6665 個字符,預計需要花費 17 分鐘才能閱讀完成。

這篇文章主要講解了“MySQL 的 crash-safe 原理是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著丸趣 TV 小編的思路慢慢深入,一起來研究和學習“MySQL 的 crash-safe 原理是什么”吧!

MySQL 作為當下最流行的開源關系型數據庫,有一個很關鍵和基本的能力,就是必須能夠保證數據不會丟。那么在這個能力背后,MySQL 是如何設計才能保證不管在什么時間崩潰,恢復后都能保證數據不會丟呢?有哪些關鍵技術支撐了這個能力?本文將為我們一一揭曉。

一、前言

MySQL  保證數據不會丟的能力主要體現在兩方面:

能夠恢復到任何時間點的狀態;

能夠保證 MySQL 在任何時間段突然奔潰,重啟后之前提交的記錄都不會丟失;

對于第一點將 MySQL 恢復到任何時間點的狀態,相信很多人都知道,只要保留有足夠的 binlog,就能通過重跑 binlog 來實現。

對于第二點的能力,也就是本文標題所講的 crash-safe。即在 InnoDB 存儲引擎中,事務提交過程中任何階段,MySQL 突然奔潰,重啟后都能保證事務的完整性,已提交的數據不會丟失,未提交完整的數據會自動進行回滾。這個能力依賴的就是 redo log 和 unod log 兩個日志。

因為 crash-safe 主要體現在事務執行過程中突然奔潰,重啟后能保證事務完整性,所以在講解具體原理之前,先了解下 MySQL 事務執行有哪些關鍵階段,后面才能依據這幾個階段來進行解析。下面以一條更新語句的執行流程為例,話不多說,直接上圖:

從上圖可以清晰地看出一條更新語句在 MySQL 中是怎么執行的,簡單進行總結一下:

從內存中找出這條數據記錄,對其進行更新;

將對數據頁的更改記錄到 redo log 中;

將邏輯操作記錄到 binlog 中;

對于內存中的數據和日志,都是由后臺線程,當觸發到落盤規則后再異步進行刷盤;

上面演示了一條更新語句的詳細執行過程,接下來咱們通過解答問題,帶著問題來剖析這個 crash-safe 的設計原理。

二、WAL 機制

問題:為什么不直接更改磁盤中的數據,而要在內存中更改,然后還需要寫日志,最后再落盤這么復雜?

這個問題相信很多同學都能猜出來,MySQL 更改數據的時候,之所以不直接寫磁盤文件中的數據,最主要就是性能問題。因為直接寫磁盤文件是隨機寫,開銷大性能低,沒辦法滿足 MySQL 的性能要求。所以才會設計成先在內存中對數據進行更改,再異步落盤。但是內存總是不可靠,萬一斷電重啟,還沒來得及落盤的內存數據就會丟失,所以還需要加上寫日志這個步驟,萬一斷電重啟,還能通過日志中的記錄進行恢復。

寫日志雖然也是寫磁盤,但是它是順序寫,相比隨機寫開銷更小,能提升語句執行的性能(針對順序寫為什么比隨機寫更快,可以比喻為你有一個本子,按照順序一頁一頁寫肯定比寫一個字都要找到對應頁寫快得多)。

這個技術就是大多數存儲系統基本都會用的 WAL(Write Ahead Log) 技術,也稱為日志先行的技術,指的是對數據文件進行修改前,必須將修改先記錄日志。保證了數據一致性和持久性,并且提升語句執行性能。

三、核心日志模塊

問題:更新 SQL 語句執行流程中,總共需要寫 3 個日志,這 3 個是不是都需要,能不能進行簡化?

更新 SQL 執行過程中,總共涉及 MySQL 日志模塊其中的三個核心日志,分別是 redo log(重做日志)、undo log(回滾日志)、binlog(歸檔日志)。這里提前預告,crash-safe 的能力主要依賴的就是這三大日志。

接下來,針對每個日志將單獨介紹各自的作用,然后再來評估是否能簡化掉。

1、重做日志 redo log

redo log 也稱為事務日志,由 InnoDB 存儲引擎層產生。記錄的是數據庫中每個頁的修改,而不是某一行或某幾行修改成怎樣,可以用來恢復提交后的物理數據頁(恢復數據頁,且只能恢復到最后一次提交的位置,因為修改會覆蓋之前的)。

前面提到的 WAL 技術,redo log 就是 WAL 的典型應用,MySQL 在有事務提交對數據進行更改時,只會在內存中修改對應的數據頁和記錄 redo log 日志,完成后即表示事務提交成功,至于磁盤數據文件的更新則由后臺線程異步處理。由于 redo log 的加入,保證了 MySQL 數據一致性和持久性(即使數據刷盤之前 MySQL 奔潰了,重啟后仍然能通過 redo log 里的更改記錄進行重放,重新刷盤),此外還能提升語句的執行性能(寫 redo log 是順序寫,相比于更新數據文件的隨機寫,日志的寫入開銷更小,能顯著提升語句的執行性能,提高并發量),由此可見 redo log 是必不可少的。

redo log 是固定大小的,所以只能循環寫,從頭開始寫,寫到末尾就又回到開頭,相當于一個環形。當日志寫滿了,就需要對舊的記錄進行擦除,但在擦除之前,需要確保這些要被擦除記錄對應在內存中的數據頁都已經刷到磁盤中了。在 redo log 滿了到擦除舊記錄騰出新空間這段期間,是不能再接收新的更新請求,所以有可能會導致 MySQL 卡頓。(所以針對并發量大的系統,適當設置 redo log 的文件大小非常重要!!!)

2、回滾日志 undo log

undo log 顧名思義,主要就是提供了回滾的作用,但其還有另一個主要作用,就是多個行版本控制 (MVCC),保證事務的原子性。在數據修改的流程中,會記錄一條與當前操作相反的邏輯日志到 undo log 中(可以認為當 delete 一條記錄時,undo log 中會記錄一條對應的 insert 記錄,反之亦然,當 update 一條記錄時,它記錄一條對應相反的 update 記錄),如果因為某些原因導致事務異常失敗了,可以借助該 undo log 進行回滾,保證事務的完整性,所以 undo log 也必不可少。

3、歸檔日志 binlog

binlog 在 MySQL 的 server 層產生,不屬于任何引擎,主要記錄用戶對數據庫操作的 SQL 語句(除了查詢語句)。之所以將 binlog 稱為歸檔日志,是因為 binlog 不會像 redo log 一樣擦掉之前的記錄循環寫,而是一直記錄(超過有效期才會被清理),如果超過單日志的最大值(默認 1G,可以通過變量 max_binlog_size 設置),則會新起一個文件繼續記錄。但由于日志可能是基于事務來記錄的 (如 InnoDB 表類型),而事務是絕對不可能也不應該跨文件記錄的,如果正好 binlog 日志文件達到了最大值但事務還沒有提交則不會切換新的文件記錄,而是繼續增大日志,所以 max_binlog_size 指定的值和實際的 binlog 日志大小不一定相等。

正是由于 binlog 有歸檔的作用,所以 binlog 主要用作主從同步和數據庫基于時間點的還原。

那么回到剛才的問題,binlog 可以簡化掉嗎?這里需要分場景來看:

如果是主從模式下,binlog 是必須的,因為從庫的數據同步依賴的就是 binlog;

如果是單機模式,并且不考慮數據庫基于時間點的還原,binlog 就不是必須,因為有 redo log 就可以保證 crash-safe 能力了;但如果萬一需要回滾到某個時間點的狀態,這時候就無能為力,所以建議 binlog 還是一直開啟;

根據上面對三個日志的詳解,我們可以對這個問題進行解答:在主從模式下,三個日志都是必須的;在單機模式下,binlog 可以視情況而定,保險起見最好開啟。

四、兩階段提交

問題:為什么 redo log 要分兩步寫,中間再穿插寫 binlog 呢?

從上面可以看出,因為 redo log 影響主庫的數據,binlog 影響從庫的數據,所以 redo log 和 binlog 必須保持一致才能保證主從數據一致,這是前提。

相信很多有過開發經驗的同學都知道分布式事務,這里的 redo log 和 binlog 其實就是很典型的分布式事務場景,因為兩者本身就是兩個獨立的個體,要想保持一致,就必須使用分布式事務的解決方案來處理。而將 redo log 分成了兩步,其實就是使用了兩階段提交協議(Two-phase Commit,2PC)。

下面對更新語句的執行流程進行簡化,看一下 MySQL 的兩階段提交是如何實現的:

從圖中可看出,事務的提交過程有兩個階段,就是將 redo log 的寫入拆成了兩個步驟:prepare 和 commit,中間再穿插寫入 binlog。

如果這時候你很疑惑,為什么一定要用兩階段提交呢,如果不用兩階段提交會出現什么情況,比如先寫 redo log,再寫 binlog 或者先寫 binlog,再寫 redo log 不行嗎?下面我們用反證法來進行論證。

我們繼續用 update T set c=c+1 where id= 2 這個例子,假設 id= 2 這一條數據的 c 初始值為 0。那么在 redo log 寫完,binlog 還沒有寫完的時候,MySQL 進程異常重啟。由于 redo log 已經寫完了,系統重啟后會通過 redo log 將數據恢復回來,所以恢復后這一行 c 的值是 1。但是由于 binlog 沒寫完就 crash 了,這時候 binlog 里面就沒有記錄這個語句。因此,不管是現在的從庫還是之后通過這份 binlog 還原臨時庫都沒有這一次更新,c 的值還是 0,與原庫的值不同。

同理,如果先寫 binlog,再寫 redo log,中途系統 crash 了,也會導致主從不一致,這里就不再詳述。

所以將 redo log 分成兩步寫,即兩階段提交,才能保證 redo log 和 binlog 內容一致,從而保證主從數據一致。

兩階段提交雖然能夠保證單事務兩個日志的內容一致,但在多事務的情況下,卻不能保證兩者的提交順序一致,比如下面這個例子,假設現在有 3 個事務同時提交:

T1 (--prepare--binlog---------------------commit)
T2 (-----prepare-----binlog----commit)
T3 (--------prepare-------binlog------commit)

解析:

    redo log prepare 的順序:T1 –》T2 –》T3

    binlog 的寫入順序:T1 –》T2 –》T3

    redo log commit 的順序:T2 –》T3 –》T1

結論:由于 binlog 寫入的順序和 redo log 提交結束的順序不一致,導致 binlog 和 redo log 所記錄的事務提交結束的順序不一樣,最終導致的結果就是主從數據不一致。

因此,在兩階段提交的流程基礎上,還需要加一個鎖來保證提交的原子性,從而保證多事務的情況下,兩個日志的提交順序一致。所以在早期的 MySQL 版本中,通過使用 prepare_commit_mutex 鎖來保證事務提交的順序,在一個事務獲取到鎖時才能進入 prepare,一直到 commit 結束才能釋放鎖,下個事務才可以繼續進行 prepare 操作。通過加鎖雖然完美地解決了順序一致性的問題,但在并發量較大的時候,就會導致對鎖的爭用,性能不佳。除了鎖的爭用會影響到性能之外,還有一個對性能影響更大的點,就是每個事務提交都會進行兩次 fsync(寫磁盤),一次是 redo log 落盤,另一次是 binlog 落盤。大家都知道,寫磁盤是昂貴的操作,對于普通磁盤,每秒的 QPS 大概也就是幾百。

五、組提交

問題:針對通過在兩階段提交中加鎖控制事務提交順序這種實現方式遇到的性能瓶頸問題,有沒有更好的解決方案呢?

答案自然是有的,在 MySQL 5.6 就引入了 binlog 組提交,即 BLGC(Binary Log Group Commit)。binlog 組提交的基本思想是,引入隊列機制保證 InnoDB commit 順序與 binlog 落盤順序一致,并將事務分組,組內的 binlog 刷盤動作交給一個事務進行,實現組提交目的。具體如圖:

第一階段(prepare 階段):

持有 prepare_commit_mutex,并且 write/fsync redo log 到磁盤,設置為 prepared 狀態,完成后就釋放 prepare_commit_mutex,binlog 不作任何操作。

第二個階段(commit 階段):這里拆分成了三步,每一步的任務分配給一個專門的線程處理:

Flush Stage(寫入 binlog 緩存)

① 持有 Lock_log mutex [leader 持有,follower 等待]

② 獲取隊列中的一組 binlog(隊列中的所有事務)

③ 寫入 binlog 緩存

Sync Stage(將 binlog 落盤)

①釋放 Lock_log mutex,持有 Lock_sync mutex[leader 持有,follower 等待]

②將一組 binlog 落盤(fsync 動作,最耗時,假設 sync_binlog 為 1)。

Commit Stage(InnoDB commit,清楚 undo 信息)

①釋放 Lock_sync mutex,持有 Lock_commit mutex[leader 持有,follower 等待]

② 遍歷隊列中的事務,逐一進行 InnoDB commit

③ 釋放 Lock_commit mutex

每個 Stage 都有自己的隊列,隊列中的第一個事務稱為 leader,其他事務稱為 follower,leader 控制著 follower 的行為。每個隊列各自有 mutex 保護,隊列之間是順序的。只有 flush 完成后,才能進入到 sync 階段的隊列中;sync 完成后,才能進入到 commit 階段的隊列中。但是這三個階段的作業是可以同時并發執行的,即當一組事務在進行 commit 階段時,其他新事務可以進行 flush 階段,實現了真正意義上的組提交,大幅度降低磁盤的 IOPS 消耗。

針對組提交為什么比兩階段提交加鎖性能更好,簡單做個總結:組提交雖然在每個隊列中仍然保留了 prepare_commit_mutex 鎖,但是鎖的粒度變小了,變成了原來兩階段提交的 1 /4,所以鎖的爭用性也會大大降低;另外,組提交是批量刷盤,相比之前的單條記錄都要刷盤,能大幅度降低磁盤的 IO 消耗。

六、數據恢復流程

問題:假設事務提交過程中,MySQL 進程突然奔潰,重啟后是怎么保證數據不丟失的?

下圖就是 MySQL 重啟后,提供服務前會先做的事 — 恢復數據的流程:

對上圖進行簡單描述就是:奔潰重啟后會檢查 redo log 中是完整并且處于 prepare 狀態的事務,然后根據 XID(事務 ID),從 binlog 中找到對應的事務,如果找不到,則回滾;找到并且事務完整則重新 commit redo log,完成事務的提交。

下面我們根據事務提交流程,在不同的階段時刻,看看 MySQL 突然奔潰后,按照上述流程是如何恢復數據的。

時刻 A(剛在內存中更改完數據頁,還沒有開始寫 redo log 的時候奔潰):

因為內存中的臟頁還沒刷盤,也沒有寫 redo log 和 binlog,即這個事務還沒有開始提交,所以奔潰恢復跟該事務沒有關系;

時刻 B(正在寫 redo log 或者已經寫完 redo log 并且落盤后,處于 prepare 狀態,還沒有開始寫 binlog 的時候奔潰):

恢復后會判斷 redo log 的事務是不是完整的,如果不是則根據 undo log 回滾;如果是完整的并且是 prepare 狀態,則進一步判斷對應的事務 binlog 是不是完整的,如果不完整則一樣根據 undo log 進行回滾;

時刻 C(正在寫 binlog 或者已經寫完 binlog 并且落盤了,還沒有開始 commit redo log 的時候奔潰):

恢復后會跟時刻 B 一樣,先檢查 redo log 中是完整并且處于 prepare 狀態的事務,然后判斷對應的事務 binlog 是不是完整的,如果不完整則一樣根據 undo log 回滾,完整則重新 commit redo log;

時刻 D(正在 commit redo log 或者事務已經提交完的時候,還沒有反饋成功給客戶端的時候奔潰):

恢復后跟時刻 C 基本一樣,都會對照 redo log 和 binlog 的事務完整性,來確認是回滾還是重新提交。

七、總結

至此對 MySQL 的 crash-safe 原理細節就基本講完了,簡單回顧一下:

首先簡單介紹了 WAL 日志先行技術,包括它的定義、流程和作用。WAL 是大部分數據庫系統實現一致性和持久性的通用設計模式。;

接著對 MySQL 的日志模塊,redo log、undo log、binlog、兩階段提交和組提交都進行了詳細介紹;

最后講解了數據恢復流程,并從不同時刻加以驗證。

感謝各位的閱讀,以上就是“MySQL 的 crash-safe 原理是什么”的內容了,經過本文的學習后,相信大家對 MySQL 的 crash-safe 原理是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是丸趣 TV,丸趣 TV 小編將為大家推送更多相關知識點的文章,歡迎關注!

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-03發表,共計6665字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 班戈县| 固镇县| 永春县| 通州市| 北宁市| 望谟县| 青阳县| 迭部县| 夏津县| 井陉县| 萨嘎县| 孟连| 靖宇县| 东明县| 永清县| 怀来县| 太仓市| 沅江市| 和龙市| 农安县| 蓝田县| 武隆县| 托克托县| 玉林市| 介休市| 龙游县| 娄底市| 镇康县| 广河县| 姚安县| 北川| 鸡泽县| 新平| 西宁市| 珠海市| 金坛市| 民勤县| 紫金县| 巢湖市| 台东市| 留坝县|