前言
2023 年某一天周末,新手程序員小明因?yàn)轭I(lǐng)導(dǎo)安排的一個(gè)活來(lái)到公司加班,小明三下五除二,按照領(lǐng)導(dǎo)要求寫(xiě)了一個(gè)跑批的數(shù)據(jù)落庫(kù)任務(wù)在測(cè)試環(huán)境執(zhí)行 ,突然間公司停電了,小明大驚,“糟了,MySQL 還在跑任務(wù),會(huì)不會(huì)因?yàn)橥蝗粩嚯?,?dǎo)致數(shù)據(jù)庫(kù)崩了”。
這時(shí)候,傍邊的同事云淡風(fēng)清的說(shuō)了一句,“沒(méi)事,小明,MySQL 有一套預(yù)寫(xiě)日志機(jī)制就是應(yīng)對(duì)這種情況的。你的落庫(kù)任務(wù)啟用了事務(wù)沒(méi),啟用了的話(huà),就等來(lái)電重新跑一下任務(wù)就行了。”
聽(tīng)了同事的話(huà),小明懸著的心放了下來(lái)。 “哦哦,我啟用了事務(wù),那我還是等周一來(lái)重新跑一遍”。
回家的公交車(chē)上,小明默默的打開(kāi)百度,搜索 MySQL 預(yù)寫(xiě)日志 ,寫(xiě)下了這篇文章 ??。
什么是預(yù)寫(xiě)日志機(jī)制?
一般情況下,大部分?jǐn)?shù)據(jù)庫(kù)都是將表和索引存儲(chǔ)在磁盤(pán)文件中。當(dāng)新增數(shù)據(jù)時(shí),數(shù)據(jù)庫(kù)系統(tǒng)會(huì)先寫(xiě)入內(nèi)存,然后將其寫(xiě)入磁盤(pán)上的數(shù)據(jù)文件。
那為什么不直接寫(xiě)入磁盤(pán)嘞?主要是每次新增都直接寫(xiě)入磁盤(pán)性能很低,放在內(nèi)存中,可以批量寫(xiě)入磁盤(pán)以提升性能。
但有一個(gè)問(wèn)題,如果數(shù)據(jù)在寫(xiě)入磁盤(pán)文件中途斷電怎么辦?當(dāng)來(lái)電恢復(fù)后,我們重啟數(shù)據(jù)庫(kù),發(fā)現(xiàn)數(shù)據(jù)不一致,又該如何處理。
所以我們需要一些其他機(jī)制來(lái)避免斷電引發(fā)的數(shù)據(jù)不一致,其實(shí) MySQL 已經(jīng)考慮到了這一點(diǎn),內(nèi)部已經(jīng)實(shí)現(xiàn)一套 WAL(預(yù)寫(xiě)日志)機(jī)制來(lái)避免這一點(diǎn)。
MySQL 設(shè)計(jì)有健壯的恢復(fù)機(jī)制,特別是使用 InnoDB 存儲(chǔ)引擎的情況下,它能夠在斷電后重啟而不會(huì)崩潰。InnoDB 存儲(chǔ)引擎使用預(yù)寫(xiě)日志(WAL)機(jī)制來(lái)確保數(shù)據(jù)的一致性和原子性。
預(yù)寫(xiě)日志機(jī)制是一種數(shù)據(jù)庫(kù)事務(wù)日志技術(shù),它要求在任何數(shù)據(jù)庫(kù)修改被寫(xiě)入到永久存儲(chǔ)(也就是磁盤(pán))之前,先將這些修改記錄到日志中。
這樣當(dāng) MySQL 遇到意外的斷電情況時(shí),它會(huì)在重啟后利用 Redo log 來(lái)恢復(fù)已提交但未寫(xiě)入數(shù)據(jù)文件的事務(wù)繼續(xù)寫(xiě)入數(shù)據(jù)文件,從而保證一致性,再利用 undo log 來(lái)撤銷(xiāo)未提交事務(wù)的需改,從而保證原子性。
MySQL 中的預(yù)寫(xiě)日志機(jī)制
在 MySQL 中,InnoDB 存儲(chǔ)引擎實(shí)現(xiàn)了 WAL 機(jī)制。包含 Redo log buffer、Redo log、Undo Log 等,來(lái)記錄事務(wù)已提交但未寫(xiě)入數(shù)據(jù)文件的數(shù)據(jù)變更以及事務(wù)回滾后的數(shù)據(jù)還原。
為了給大家講清楚 MySQL 的預(yù)寫(xiě)日志機(jī)制,會(huì)涉及到 MySQL 架構(gòu)中的以下內(nèi)容,
Buffer Pool(緩沖池)
Buffer Pool (緩沖池)是 InnoDB 存儲(chǔ)引擎中非常重要的內(nèi)存結(jié)構(gòu),顧名思義,緩沖池就是起到一個(gè)緩存的作用,因?yàn)槲覀兌贾?MySQL 的數(shù)據(jù)最終是存儲(chǔ)在磁盤(pán)中的,如果沒(méi)有這個(gè) Buffer Pool 那么我們每次的數(shù)據(jù)庫(kù)請(qǐng)求都會(huì)磁盤(pán)中查找,這樣必然會(huì)存在 IO 操作,這肯定是無(wú)法接受的。
但是有了 Buffer Pool 就是我們第一次在查詢(xún)的時(shí)候會(huì)將查詢(xún)的結(jié)果存到 Buffer Pool 中,這樣后面再有請(qǐng)求的時(shí)候就會(huì)先從緩沖池中去查詢(xún),如果沒(méi)有再去磁盤(pán)中查找,然后在放到 Buffer Pool 中。
Redo log buffer(日志緩沖區(qū))
Redo log buffer 是用作數(shù)據(jù)變更記錄寫(xiě)入 Redo log 文件前的一塊內(nèi)存區(qū)域。日志緩沖區(qū)大小由 innodb_log_buffer_size 變量定義,默認(rèn)大小為 16MB。
日志緩沖區(qū)的內(nèi)容會(huì)定期刷新到 Redo log 文件中,大型日志緩沖區(qū)允許大型事務(wù)運(yùn)行,而無(wú)需在事務(wù)提交之前將 Redo log 數(shù)據(jù)寫(xiě)入磁盤(pán)。因此如果事務(wù)涉及的更新、插入或刪除操作數(shù)據(jù)量較大時(shí),可以增加日志緩沖區(qū)的大小可以節(jié)省磁盤(pán) I/O。
MySQL 提交事務(wù)的時(shí)候,會(huì)將 Redo log buffer 中的數(shù)據(jù)寫(xiě)入到 Redo log 文件中,刷磁盤(pán)可以通過(guò) innodb_flush_log_at_trx_commit 參數(shù)來(lái)設(shè)置
- 值為 0 表示不刷入磁盤(pán)
- 值為 1 表示立即刷入磁盤(pán)
- 值為 2 表示先刷到 os cache
為了提高性能,MySQL 首先將修改操作寫(xiě)入到日志緩沖區(qū),之后以 innodb_flush_log_at_trx_commit 參數(shù)設(shè)置落盤(pán)時(shí)機(jī),將日志緩沖區(qū)刷入到磁盤(pán)的 Redo log 文件中去。
Redo Log
MySQL Redo Log 是 InnoDB 存儲(chǔ)引擎中的一個(gè)重要組件,它是一種磁盤(pán)基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),用于在崩潰重啟期間修復(fù)由已提交事務(wù)但未寫(xiě)入數(shù)據(jù)文件的數(shù)據(jù)。
在正常操作中,Redo log 記錄了由 SQL 語(yǔ)句執(zhí)行導(dǎo)致的表數(shù)據(jù)變更記錄。將 Redo log buffer 中的數(shù)據(jù)持久化到磁盤(pán)中,就是將 Redo log buffer 中的數(shù)據(jù)寫(xiě)入到 Redo log 磁盤(pán)文件中。
數(shù)據(jù)在由 Redo log buffer 寫(xiě)入 Redo log 時(shí)的觸發(fā)時(shí)機(jī)如下,
- MySQL 正常關(guān)閉時(shí)觸發(fā)
- 當(dāng) Redo log buffer 中記錄的寫(xiě)入量大于 Redo log buffer 內(nèi)存空間的一半時(shí),會(huì)觸發(fā)落盤(pán)
- InnoDB 的后臺(tái)線(xiàn)程每隔 1 秒,將 Redo log buffer 持久化到磁盤(pán)
- 每次事務(wù)提交時(shí)都將緩存在 redo log buffer 里的 redo log 直接持久化到磁盤(pán)(這個(gè)策略就是由上文提高 innodb_flush_log_at_trx_commit 參數(shù)控制)
Redo log 是 WAL 機(jī)制的核心,它記錄了事務(wù)所做的所有修改。如果數(shù)據(jù)庫(kù)發(fā)生故障,可以使用 Redo 日志來(lái)重做事務(wù),從而確保數(shù)據(jù)的一致性。
Undo Log
Undo Log 記錄了如何撤銷(xiāo)一個(gè)事務(wù)的修改。如果需要回滾事務(wù)或在執(zhí)行事務(wù)時(shí)還未提交,數(shù)據(jù)庫(kù)就發(fā)生了崩潰,這時(shí)我們就需要將未提交事務(wù)前的數(shù)據(jù)回滾回去,難道這個(gè)操作有我們自己來(lái)做嗎?顯然 MySQL 也考慮到了這一點(diǎn)。
MySQL 會(huì)使用 Undo log 來(lái)撤銷(xiāo)未提交的修改。在操作數(shù)據(jù)前,MySQL 首先將數(shù)據(jù)備份到 Undo log,然后進(jìn)行數(shù)據(jù)修改。
如果出現(xiàn)錯(cuò)誤或者用戶(hù)執(zhí)行了 Rollback 語(yǔ)句,系統(tǒng)可以利用 Undo log 中的備份將數(shù)據(jù)恢復(fù)到事務(wù)操作前的狀態(tài)。
通過(guò) Undo log 撤銷(xiāo)修改,從而確保數(shù)據(jù)的原子性。
結(jié)合 Buffer Pool、Redo log buffer、Redo log、Undo log 后,我們?cè)?strong>MySQL 中更新一條數(shù)據(jù)的流程如下,
- 準(zhǔn)備更新一條 SQL 語(yǔ)句
- MySQL(innodb)會(huì)先去緩沖池(Buffer Pool)中去查找這條數(shù)據(jù),沒(méi)找到就會(huì)去磁盤(pán)中查找,如果查找到就會(huì)將這條數(shù)據(jù)加載到緩沖池(Buffer Pool)中
- 在加載到 Buffer Pool 的同時(shí),會(huì)將這條數(shù)據(jù)的原始記錄保存到 undo 日志文件中
- innodb 會(huì)在 Buffer Pool 中執(zhí)行更新操作
- 更新后的數(shù)據(jù)會(huì)記錄在 Redo log buffer 中
- MySQL 提交事務(wù)的時(shí)候,會(huì)將 Redo log buffer 中的數(shù)據(jù)寫(xiě)入到 Redo log 文件中,刷磁盤(pán)可以通過(guò) innodb_flush_log_at_trx_commit 參數(shù)來(lái)設(shè)置
- MySQL 重啟的時(shí)候會(huì)將 Redo log 恢復(fù)到緩沖池中
額外知識(shí):檢查點(diǎn)(Checkpoint)
檢查點(diǎn)是什么?為什么有了 Redo log、Undo log 還要引入檢查點(diǎn)。
明明借助 Redo log、Undo log 我們就可以實(shí)現(xiàn) MySQL 的故障恢復(fù)了。
雖然數(shù)據(jù)在寫(xiě)入 Redo log 文件后,就代表數(shù)據(jù)變更已經(jīng)生效了,但是還未寫(xiě)入到數(shù)據(jù)文件,也就是還沒(méi)有完成事務(wù)的持久性。
那么檢查點(diǎn)就是幫助 MySQL 實(shí)現(xiàn)事務(wù)的持久性。
如果說(shuō) Redo log 可以無(wú)限地增大,能夠保存所有數(shù)據(jù)庫(kù)變更的數(shù)據(jù),那么在發(fā)生宕機(jī)時(shí)完全可以通過(guò) Redo log 來(lái)恢復(fù)數(shù)據(jù)庫(kù)系統(tǒng)的數(shù)據(jù)到宕機(jī)發(fā)生前的情況。
然而現(xiàn)實(shí)是我們的物理磁盤(pán)文件大小是有效的。即使達(dá)成無(wú)限了,如果數(shù)據(jù)庫(kù)運(yùn)行了很久后發(fā)生宕機(jī),那么使用 Redo log 進(jìn)行恢復(fù)的時(shí)間也會(huì)非常的久。
所以在 Redo log 文件容量是有限的情況下,還需要定期將 Redo log 寫(xiě)入數(shù)據(jù)文件完成數(shù)據(jù)的持久化,在這樣的情況下,就引入了 Checkpoint(檢查點(diǎn))技術(shù)。
Checkpoint(檢查點(diǎn))技術(shù)不僅僅是會(huì)同步 Redo log 寫(xiě)入數(shù)據(jù)文件,也會(huì)同步臟頁(yè)數(shù)據(jù)寫(xiě)入數(shù)據(jù)文件。
檢查點(diǎn)的觸發(fā)時(shí)機(jī)有兩種如下,
Sharp Checkpoint(完全檢查點(diǎn))
將內(nèi)存中所有臟頁(yè)全部寫(xiě)到磁盤(pán)就是完全檢查點(diǎn),比如數(shù)據(jù)庫(kù)實(shí)例關(guān)閉時(shí)。
Fuzzy Checkpoint(模糊檢查點(diǎn))
將部分臟頁(yè)刷新到磁盤(pán),就是模糊檢查點(diǎn),一般就是臟頁(yè)達(dá)到一定數(shù)量時(shí)觸發(fā)。數(shù)據(jù)庫(kù)實(shí)例運(yùn)行過(guò)程產(chǎn)生的檢查基本上就是這種類(lèi)型的檢查點(diǎn)。
因此其實(shí) Checkpoint 就是指一個(gè)觸發(fā)點(diǎn)(時(shí)間點(diǎn)),當(dāng)發(fā)生 Checkpoint 時(shí),會(huì)將臟頁(yè)寫(xiě)回磁盤(pán),以確保數(shù)據(jù)的持久性和一致性。并且 Redo log、Undo log 文件也可以重新覆寫(xiě),這樣可以保證重啟時(shí)不會(huì)因?yàn)?Redo log、Undo log 文件太大而導(dǎo)致重啟時(shí)間過(guò)長(zhǎng)。
斷電故障恢復(fù)案例
OK,假如我們正在使用 MySQL 添加數(shù)據(jù)。在提交事務(wù)的過(guò)程中,突然發(fā)生了斷電,那么這個(gè)數(shù)據(jù)會(huì)丟嗎?
我們結(jié)合上文MySQL 中更新一條數(shù)據(jù)的流程,來(lái)給大家分析下具體場(chǎng)景,
數(shù)據(jù)在寫(xiě)入 Buffer Pool、Redo log buffer 中時(shí),發(fā)生斷電
先說(shuō)結(jié)論,會(huì)丟。因?yàn)閿?shù)據(jù)沒(méi)有寫(xiě)入 Redo log 前,MySQL 是沒(méi)辦法保證數(shù)據(jù)一致性的。但是這沒(méi)關(guān)系的,因?yàn)?MySQL 會(huì)認(rèn)為本次事務(wù)是失敗的,在重啟后可以根據(jù) Undo log 文件將數(shù)據(jù)恢復(fù)到更新前的樣子,并不會(huì)有任何的影響。
數(shù)據(jù)在寫(xiě)入 Redo log 文件后,發(fā)生斷電
先說(shuō)結(jié)論,不會(huì)丟。因?yàn)?Redo log buffer 中的數(shù)據(jù)已經(jīng)被寫(xiě)入到 Redo log 了,就算數(shù)據(jù)庫(kù)宕機(jī)了,在下次重啟的時(shí)候 MySQL 也會(huì)將 Redo log 文件內(nèi)容恢復(fù)到 Buffer Pool 中進(jìn)行重放。
參考資料
- https://xiaolincoding.com/mysql/log/how_update.html#redo-log-文件寫(xiě)滿(mǎn)了怎么辦
- https://pdai.tech/md/db/sql-mysql/sql-mysql-execute.html
- https://zhuanlan.zhihu.com/p/552706911?utm_medium=referral
最后說(shuō)兩句
預(yù)寫(xiě)日志機(jī)制是數(shù)據(jù)庫(kù)管理系統(tǒng)中保證數(shù)據(jù)安全性的關(guān)鍵技術(shù)。在 MySQL 中,通過(guò) InnoDB 存儲(chǔ)引擎實(shí)現(xiàn)的 WAL 機(jī)制,即使在發(fā)生斷電等意外情況下,也能夠有效地保護(hù)數(shù)據(jù)不受損壞。這使得 MySQL 成為了一個(gè)可靠和健壯的數(shù)據(jù)庫(kù)解決方案,適用于各種需要高數(shù)據(jù)一致性和可靠性的應(yīng)用場(chǎng)景。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-747906.html
關(guān)注公眾號(hào)【waynblog】每周分享技術(shù)干貨、開(kāi)源項(xiàng)目、實(shí)戰(zhàn)經(jīng)驗(yàn)、國(guó)外優(yōu)質(zhì)文章翻譯等,您的關(guān)注將是我的更新動(dòng)力!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-747906.html
到了這里,關(guān)于糟了,數(shù)據(jù)庫(kù)崩了,又好像沒(méi)崩的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!