自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

全網(wǎng)最通透:MySQL 的 redo log 保證數(shù)據(jù)不丟的原理

數(shù)據(jù)庫(kù) 新聞
本文主要講 MySQL 內(nèi)部 XA 事務(wù)中 redo log 兩段式提交的細(xì)節(jié)。

總會(huì)有面試官問(wèn):你知道 MySQL 如何保障數(shù)據(jù)不丟的嗎?實(shí)際上這個(gè)問(wèn)題是十分不準(zhǔn)確的,MySQL 保障數(shù)據(jù)不丟的手段可太多了。但通常面試官想聽(tīng)的內(nèi)容就是 redo log 兩段式提交是如何保障數(shù)據(jù)不丟的。(不過(guò)個(gè)人感覺(jué)這么說(shuō)還是不太準(zhǔn)確)

所謂「redo log」,意即「重做日志」,也就是用來(lái)恢復(fù)數(shù)據(jù)用的日志。所謂「兩段式提交」,也被稱(chēng)作「兩階段提交」(Two-Phase Commit,簡(jiǎn)稱(chēng) 2PC)。本文主要講 MySQL 內(nèi)部 XA 事務(wù)中 redo log 兩段式提交的細(xì)節(jié)。為了讓大家飽餐一頓,我會(huì)先為大家上億點(diǎn)點(diǎn)前菜,雖然有點(diǎn)多,但是相信會(huì)很開(kāi)胃。

開(kāi)胃前菜

你知道什么是存儲(chǔ)引擎、隨機(jī) IO 和順序 IO嗎?你知道 MySQL 中的緩沖池嗎?binlog、redo log 聽(tīng)說(shuō)過(guò)嗎?都有什么用?什么?你都不知道?面試結(jié)束了。

什么是存儲(chǔ)引擎?

存儲(chǔ)引擎是 MySQL 中直接與磁盤(pán)交互部分。頁(yè)是存儲(chǔ)引擎讀寫(xiě)數(shù)據(jù)的最小單位,一個(gè)頁(yè)里可以有一條或多條表記錄。MySQL 中的存儲(chǔ)引擎有很多種,比如 InnoDB、MyISAM、Memory 等。其中最常用的是 InnoDB。而 InnoDB 是 MySQL 中唯一能夠完整支持事務(wù)特性的存儲(chǔ)引擎,也是一個(gè)高性能的存儲(chǔ)引擎。本文要講的「兩段式提交」就發(fā)生在 InnoDB 中。

什么是隨機(jī) IO 和順序 IO?

磁盤(pán)讀寫(xiě)數(shù)據(jù)的兩種方式。隨機(jī) IO 需要先找到地址,再讀寫(xiě)數(shù)據(jù),每次拿到的地址都是隨機(jī)的。就像送外賣(mài),每一單送的地址都不一樣,到處跑,效率極低。而順序 IO,由于地址是連貫的,找到地址后,一次可以讀寫(xiě)許多數(shù)據(jù),效率比較高。就像送外賣(mài),所有的單子地址都在一棟樓,一下可以送很多,效率很高。

什么是緩沖池?

關(guān)系型數(shù)據(jù)庫(kù)的特點(diǎn)就是需要對(duì)磁盤(pán)中大量的數(shù)據(jù)進(jìn)行存取,所以有時(shí)候也被叫做基于磁盤(pán)的數(shù)據(jù)庫(kù)。正是因?yàn)閿?shù)據(jù)庫(kù)需要頻繁對(duì)磁盤(pán)進(jìn)行 IO 操作,為了改善因?yàn)橹苯幼x寫(xiě)磁盤(pán)導(dǎo)致的 IO 性能問(wèn)題,所以引入了緩沖池。

緩沖池是一片內(nèi)存區(qū)域,存儲(chǔ)引擎在讀取數(shù)據(jù)時(shí),會(huì)先將頁(yè)讀取到緩沖池中。下次讀取時(shí),先判斷是否在緩沖池,如果在,則直接讀取,否則從磁盤(pán)中讀取。在修改數(shù)據(jù)時(shí),如果緩沖池中不存在所需的數(shù)據(jù)頁(yè),則從磁盤(pán)讀入緩沖池,否則直接對(duì)緩沖池中的數(shù)據(jù)頁(yè)進(jìn)行修改。

這樣的好處是,如果我們頻繁修改某一個(gè)位于磁盤(pán)的數(shù)據(jù)頁(yè),我們可以不用每次都去磁盤(pán)讀寫(xiě)(注意是讀和寫(xiě))該頁(yè),而是直接對(duì)緩沖池中的內(nèi)容修改,在一定的時(shí)機(jī)再把數(shù)據(jù)刷新到磁盤(pán)。這樣就會(huì)使得對(duì)磁盤(pán)的多次操作變?yōu)橐淮巍<幢阈薷牡膬?nèi)容在磁盤(pán)中相距較遠(yuǎn)的不同數(shù)據(jù)頁(yè)上,我們也可以將對(duì)多次對(duì)磁盤(pán)的 IO 合并為一次隨機(jī) IO。被修改的數(shù)據(jù)頁(yè)會(huì)與磁盤(pán)上的數(shù)據(jù)產(chǎn)生短暫的不一致,我們稱(chēng)此時(shí)緩沖池中的數(shù)據(jù)頁(yè)為 臟頁(yè) ,將該頁(yè)刷到磁盤(pán)的操作稱(chēng)為 刷臟頁(yè) (本句是重點(diǎn),后面要吃)。這個(gè)刷臟頁(yè)的時(shí)機(jī)我們看看就好:[^1]

innodb_max_dirty_pages_pct

由于這個(gè)刷臟頁(yè)的過(guò)程還是異步的,這樣更新操作就不需要等待磁盤(pán)的 IO 操作了。因此這些特點(diǎn)極大地提升了 InnoDB 的性能。

什么是binlog?

binlog 是 MySQL 服務(wù)器層面實(shí)現(xiàn)的一種二進(jìn)制日志,用于記錄所有對(duì)數(shù)據(jù)庫(kù)的更改操作(這種日志被稱(chēng)為邏輯日志)。比如你 update 一條記錄,服務(wù)器就會(huì)記錄一條對(duì)應(yīng)的信息到 binlog。但在 InnoDB 中,這個(gè) binlog 是以事務(wù)為單位刷新到磁盤(pán)的[^2]?;?binlog 的這種特性,一般我們會(huì)將 binlog 用于以下幾個(gè)方面:[^2]

數(shù)據(jù)庫(kù)增量備份與恢復(fù):在使用備份還原數(shù)據(jù)后,可以使用 binlog 中記錄的內(nèi)容對(duì)備份時(shí)間點(diǎn)(簡(jiǎn)稱(chēng)備份點(diǎn))后的數(shù)據(jù)進(jìn)行恢復(fù)。因?yàn)?binlog 會(huì)還會(huì)記錄下更改操作的時(shí)間,所以 binlog 可以恢復(fù)到某一具體時(shí)間點(diǎn)的數(shù)據(jù)。這就為我們刪庫(kù)后提供了除跑路以外的第二個(gè)選項(xiàng):使用 binlog 恢復(fù)數(shù)據(jù)。

主從復(fù)制:MySQL 從服務(wù)器可以通過(guò)訂閱 binlog 實(shí)現(xiàn)對(duì)主服務(wù)器的增量復(fù)制。

審計(jì):通過(guò)對(duì) binlog 中的數(shù)據(jù)進(jìn)行審計(jì),判斷是否存在安全問(wèn)題,比如 SQL 注入。

使用 binlog 進(jìn)行恢復(fù)的流程是:[^5]

  1. 先通過(guò)最新的備份恢復(fù)數(shù)據(jù)庫(kù)的數(shù)據(jù),并記錄下備份文件備份的時(shí)間點(diǎn)。
  2. 在 binlog 中找到這個(gè)時(shí)間點(diǎn),提取這個(gè)時(shí)間點(diǎn)以后的數(shù)據(jù)用于實(shí)現(xiàn)對(duì)備份點(diǎn)后數(shù)據(jù)的恢復(fù)(這個(gè)特性被稱(chēng)為 Point in Time,簡(jiǎn)稱(chēng) PIT)。

各個(gè)部分之間的關(guān)系

正餐開(kāi)始

食欲打開(kāi)了,后面的內(nèi)容我們就能吃的下了。

什么是 redo log?

前面我們講到數(shù)據(jù)頁(yè)在緩沖池中被修改會(huì)變成臟頁(yè)。如果這時(shí)宕機(jī),臟頁(yè)就會(huì)失效,這就導(dǎo)致我們修改的數(shù)據(jù)丟失了,也就無(wú)法保證事務(wù)的持久性。保證數(shù)據(jù)不丟,就是 redo log 的一個(gè)重要功能。我們已經(jīng)了解,如果我們修改了緩沖池中的數(shù)據(jù)頁(yè)就立刻刷臟頁(yè),會(huì)產(chǎn)生大量隨機(jī) IO,導(dǎo)致磁盤(pán)性能變差;但如果我們先寫(xiě)緩沖,一段時(shí)間后再刷臟頁(yè),就有可能造成數(shù)據(jù)丟失,無(wú)法保證事務(wù)的持久性。這可有點(diǎn)難了。

于是救世主來(lái)了,救世主的名字叫 WAL(Write-Ahead Logging,日志先行) 。即:事務(wù)提交前先寫(xiě)日志,再修改頁(yè)(修改頁(yè)的時(shí)機(jī)就是刷臟頁(yè)的時(shí)機(jī))。這里所謂的日志,就是 redo log。redo log 不會(huì)記錄對(duì)整個(gè)頁(yè)的修改,而是大概像這種:

xx 表空間,xx 頁(yè),xx 位置,xx 

記錄下對(duì)磁盤(pán)中某某頁(yè)某某位置數(shù)據(jù)的修改結(jié)果(這種日志被稱(chēng)為物理日志),這樣會(huì)節(jié)省很多磁盤(pán)空間。 由于 redo log 是順序?qū)懀樞?IO),因此能有效提升 IO 效率;又因?yàn)槊看问聞?wù)提交前會(huì)先寫(xiě) redo log,因此可以保障更新的數(shù)據(jù)不丟失。

我們知道,一旦臟頁(yè)刷新,磁盤(pán)上對(duì)應(yīng)的 redo log 就會(huì)失效,所以 redo log 用完后,可以再回頭使用,這樣更節(jié)省空間。直到需要刷 redo log buffer 時(shí)發(fā)現(xiàn)接下來(lái)的 redo log 對(duì)應(yīng)的臟頁(yè)未被刷新,此時(shí)會(huì)強(qiáng)制刷新臟頁(yè)。緩沖池的好處我們前面已經(jīng)講過(guò),所以 redo log 弄了個(gè)類(lèi)似作用的 redo log buffer。在寫(xiě) redo log 時(shí)會(huì)先寫(xiě) redo log buffer,并在以下時(shí)機(jī)將 redo log 刷新到磁盤(pán):[^3]

  • 每秒刷新一次
  • 事務(wù)提交時(shí)
  • redo log buffer 剩余空間小于 1/2 時(shí)

我們理應(yīng)想到,如果臟頁(yè)沒(méi)刷完,數(shù)據(jù)庫(kù)宕機(jī)了,那么必然是需要使用 redo log 來(lái)恢復(fù)數(shù)據(jù)的。那么 redo log 應(yīng)該從哪開(kāi)始恢復(fù)數(shù)據(jù)呢?為解決這個(gè)問(wèn)題 InnoDB 為 redo log 記錄了序列號(hào),這被稱(chēng)為 LSN(Log Sequence Number),可以理解為偏移量,越新的日志 LSN 越大。InnoDB 用檢查點(diǎn)( checkpoint_lsn? )指示未被刷盤(pán)的數(shù)據(jù)從這里開(kāi)始,用 lsn? 指示下一個(gè)應(yīng)該被寫(xiě)入日志的位置。不過(guò)由于有 redo log buffer 的緣故,實(shí)際被寫(xiě)入磁盤(pán)的位置往往比 lsn 要小。

為了大家能有個(gè)更整體的概念,咱們?cè)俣喑砸坏琅洳耍簎ndo log。InnoDB 能夠保證對(duì)事務(wù)的完整支持,這主要就得益于 redo log 和 undo log。redo log 我們講了,能夠保證緩沖池中被修改的數(shù)據(jù)頁(yè)不丟以及在數(shù)據(jù)庫(kù)宕機(jī)后對(duì)丟失的數(shù)據(jù)進(jìn)行自動(dòng)恢復(fù)。而 undo log 則用于實(shí)現(xiàn) MVCC 和事務(wù)回滾。在事務(wù)執(zhí)行的過(guò)程中,不但會(huì)記錄 redo log,還會(huì)記錄 undo log。至于更多細(xì)節(jié),大家自行去了解吧。

那么 redo log 到底如何保障數(shù)據(jù)不丟的?

如何保障數(shù)據(jù)不丟?

假設(shè)我們有一個(gè)表 t1,數(shù)據(jù)如下:

mysql> select * from t1;
+----+------+
| id | name |
+----+------+
| 1 | a |
+----+------+

當(dāng)我們執(zhí)行如下 update 語(yǔ)句時(shí):

mysql begin; update t1 set name='aa' where id=1; commit;

InnoDB 內(nèi)部的流程是這樣的:

  1. 服務(wù)器收到事務(wù)開(kāi)始的指令,為事務(wù)生成一個(gè)全局唯一的事務(wù) id。這個(gè)事務(wù) id 在記錄 binlog 和 redo log 時(shí)都會(huì)使用。
  2. 如果緩存池中沒(méi)有 id=1 所在數(shù)據(jù)頁(yè)的數(shù)據(jù),從磁盤(pán)中找到對(duì)應(yīng)的數(shù)據(jù)頁(yè)(注意,這里是一個(gè)數(shù)據(jù)頁(yè),不是一條記錄),把數(shù)據(jù)頁(yè)加載到緩存。
  3. 修改緩存數(shù)據(jù)頁(yè)中 id=1 的數(shù)據(jù)。
  4. 記錄數(shù)據(jù)到 redo log buffer[^4]、binlog cache[^2]。根據(jù) redo log 刷盤(pán)的策略,這個(gè)過(guò)程中 redo log buffer 可能會(huì)被刷新到磁盤(pán)。
  5. 服務(wù)器收到事務(wù)提交的指令。
  6. 刷新 redo log buffer 到磁盤(pán),并標(biāo)記該事務(wù)的狀態(tài)為 prepare。此操作稱(chēng)為 redo log prepare。
  7. 刷新 binlog cache 到磁盤(pán)。
  8. 刷新 redo log buffer 到磁盤(pán),并標(biāo)記該事務(wù)的狀態(tài)為 commit。此操作稱(chēng)為 redo log commit。
  9. 向客戶端返回事務(wù)執(zhí)行的結(jié)果。

這樣 redo log 先 prepare,再刷新 binlog ,再 redo log commit 的過(guò)程就是一次兩段式提交。這種只在 MySQL 內(nèi)部組件間保障數(shù)據(jù)一致性的操作,也被稱(chēng)作內(nèi)部 XA 事務(wù);與之對(duì)應(yīng)的是,保障跨服務(wù)器間數(shù)據(jù)一致性的兩段式提交,被稱(chēng)為外部 XA 事務(wù),即分布式事務(wù)。

注:XA 事務(wù)屬于分布式事務(wù)中兩段式提交事務(wù)的一種實(shí)現(xiàn)

在宕機(jī)后,重啟 MySQL 時(shí),InnoDB 會(huì)自動(dòng)恢復(fù) redo log 中 checkpoint_lsn 后的,且處于 commit 狀態(tài)的事務(wù)。如果 redo log 中事務(wù)的狀態(tài)為 prepare,則需要先查看 binlog 中該事務(wù)是否存在,是的話就恢復(fù),否則就回滾(通過(guò) undo log 回滾。臟頁(yè)一直在刷,更新了臟頁(yè),但事務(wù)沒(méi)提交就宕機(jī)了,所以需要回滾)。

消化一下

發(fā)生宕機(jī)怎么辦?

MySQL 宕機(jī)可能會(huì)發(fā)生在整個(gè)過(guò)程中的任意時(shí)刻。以剛才的流程為例,假設(shè)宕機(jī)發(fā)生在第 5 步后、第 6 步前。此時(shí)服務(wù)器還未向客戶端返回事務(wù)的結(jié)果,而 redo log 中可能記錄了該事務(wù)的 redo log,也可能沒(méi)有。但是只要該事務(wù)沒(méi)有被標(biāo)記為 prepare,我們就認(rèn)為該事務(wù)沒(méi)有執(zhí)行完,否則 redo log 用于恢復(fù)事務(wù)的數(shù)據(jù)可能是不完整的。因此,只要此時(shí)我們選擇拋棄未 prepare 的 redo log,不會(huì)導(dǎo)致任何數(shù)據(jù)一致性的問(wèn)題。

那么后面的步驟宕機(jī)會(huì)怎樣呢?這就涉及到為什么非得要兩階段提交了。

為什么非得要兩階段提交?

在說(shuō)明以前,我們還需要弄清兩個(gè)問(wèn)題:

  1. 有 binlog 為什么還要 redo log ?
  2. 有 redo log 為什么還要 binlog?

有 binlog 為什么還要 redo log ?

  1. binlog 不知道數(shù)據(jù)庫(kù)究竟是在哪一時(shí)刻丟失了哪部分?jǐn)?shù)據(jù),只能從備份點(diǎn)開(kāi)始對(duì) binlog 記錄重放來(lái)恢復(fù)數(shù)據(jù),比較耗時(shí)。
  2. binlog 恢復(fù)是需要我們手動(dòng)執(zhí)行的,而 redo log 可以在服務(wù)器重啟后自動(dòng)恢復(fù)數(shù)據(jù)。
  3. WAL + 先寫(xiě)緩沖 + 異步刷臟頁(yè)有效提升了磁盤(pán)的 IO 效率。

有 redo log 為什么還要 binlog?

  1. binlog 是服務(wù)器層面的功能,redo log 是 innoDB 的功能。redo log 幫助 InnoDB 實(shí)現(xiàn)了性能提升、自動(dòng)恢復(fù)。但其他存儲(chǔ)引擎是無(wú)法使用 redo log 的能力的。
  2. 我們也可以關(guān)閉 binlog,但大多數(shù)情況下我們都會(huì)開(kāi)啟,因?yàn)殚_(kāi)啟的好處更多。比如,主從模式需要訂閱 binlog 進(jìn)行主從復(fù)制,以及可以通過(guò) binlog 進(jìn)行數(shù)據(jù)庫(kù)的增量備份和恢復(fù)。

redo log 有很多好處,所以我們不能放棄;binlog 也有很多好處,我們也不能放棄。也就是說(shuō),這兩個(gè)功能我們都需要開(kāi)啟。既然都要開(kāi)啟,那么 我們必須保證 redo log 和 binlog 數(shù)據(jù)的一致性。 如果 binlog 有 redo log 沒(méi)有,那么 redo log 宕機(jī)自動(dòng)恢復(fù)時(shí)的數(shù)據(jù)就會(huì)缺少;反之,redo log 有,binlog 沒(méi)有,如果開(kāi)啟了主從模式,主服務(wù)器因?yàn)?redo log 恢復(fù)了數(shù)據(jù),但從服務(wù)器靠消費(fèi) binlog 保證和主服務(wù)器數(shù)據(jù)一致,這就導(dǎo)致從服務(wù)器比主服務(wù)器數(shù)據(jù)少。

那么為什么非得要寫(xiě)兩次,我們能不能只寫(xiě)一次 redo log?

這樣仍然會(huì)有不一致問(wèn)題。比方說(shuō),先寫(xiě) binlog 再寫(xiě) redo log:

此時(shí)如果有大量并發(fā),我們 binlog 噌噌噌往上寫(xiě),redo log 還沒(méi)寫(xiě)完,宕機(jī)機(jī)了,兩者的數(shù)據(jù)就會(huì)出現(xiàn)大量不一致現(xiàn)象。此外,因?yàn)?binlog 數(shù)據(jù)最完整,這樣會(huì)導(dǎo)致我們必須從 binlog 回滾,而且還得是手動(dòng)回滾。InnoDB 本來(lái)是可以自恢復(fù)的存儲(chǔ)引擎,這樣一來(lái),自恢復(fù)的特性不是沒(méi)了,redo log 不是白開(kāi)發(fā)了?使用 binlog 恢復(fù) redo log 更不用想了,因?yàn)?binlog 根本不知道從何處開(kāi)始恢復(fù)(它沒(méi)有 checkpoint_lsn )。

再說(shuō)先寫(xiě) redo log 再寫(xiě) binlog:

不一致性的問(wèn)題與上述內(nèi)容相似。另外還會(huì)導(dǎo)致 redo log 在恢復(fù)時(shí),每次都需要去 binlog 查看該事務(wù)是否已寫(xiě)入,嚴(yán)重影響性能。而如果是兩階段提交,處于 commit 階段的事務(wù)都會(huì)直接恢復(fù),處于 prepare 階段才需要去看 binlog。

那用 redo log 恢復(fù) binlog 不行嗎?

第一,binlog 是服務(wù)器的特性,redo log 是 InnoDB 的特性,兩者并不在一個(gè)層面上,能不能這么做,很難說(shuō)。第二,即便可以,也增加了很大的復(fù)雜度, redo log 中記錄的數(shù)據(jù)(物理日志)能不能復(fù)原 SQL 語(yǔ)句,如何復(fù)原,這都是需要思考的問(wèn)題。遠(yuǎn)遠(yuǎn)不如直接使用兩階段提交方便。

兩段式提交會(huì)不會(huì)影響性能?

InnoDB 使用了組提交的方式,盡量降低了兩階段提交帶來(lái)的性能影響。在并發(fā)事務(wù)較多的情況下,MySQL 會(huì)將多個(gè)事務(wù)的 redo log 放在一起提交,大大節(jié)省了磁盤(pán) IO。具體就不在此展開(kāi)了。binlog 刷盤(pán)時(shí)同樣也會(huì)采取類(lèi)似的策略。

吃點(diǎn)飯后甜點(diǎn)吧

如果你搞明白了上面的內(nèi)容,你會(huì)發(fā)現(xiàn)「基于事務(wù)消息的分布式事務(wù)」使用的就是典型的 2PC 思想,你又會(huì)發(fā)現(xiàn)「基于本地消息的分布式事務(wù)」使用的就是典型的 WAL 思想。如果你不了解,馬上去學(xué)一下吧!

責(zé)任編輯:張燕妮 來(lái)源: 知乎專(zhuān)欄
相關(guān)推薦

2024-03-14 14:18:58

MySQL業(yè)務(wù)設(shè)計(jì)事務(wù)

2025-01-20 08:20:00

redo logMySQL數(shù)據(jù)庫(kù)

2021-01-26 13:47:08

MySQL存儲(chǔ)數(shù)據(jù)

2020-08-20 12:10:42

MySQL日志數(shù)據(jù)庫(kù)

2024-05-30 08:03:17

2024-06-11 00:00:02

MySQL數(shù)據(jù)庫(kù)系統(tǒng)

2021-03-08 10:19:59

MQ消息磁盤(pán)

2021-07-28 08:32:03

MySQLRedo存儲(chǔ)

2019-05-06 15:27:48

Oracle數(shù)據(jù)庫(kù)數(shù)據(jù)

2020-10-26 09:19:11

線程池消息

2024-04-12 14:04:17

機(jī)器學(xué)習(xí)DNN

2024-05-28 00:10:00

JavaMySQL數(shù)據(jù)庫(kù)

2021-10-04 09:23:30

Redo日志內(nèi)存

2025-01-15 13:19:09

MySQL日志事務(wù)

2024-12-16 00:00:05

MySQL二進(jìn)制數(shù)據(jù)

2023-11-23 13:17:39

MySQL?數(shù)據(jù)庫(kù)

2024-02-26 08:10:00

Redis數(shù)據(jù)數(shù)據(jù)庫(kù)

2024-11-11 07:05:00

Redis哨兵模式主從復(fù)制

2024-07-29 11:50:50

2024-08-20 08:40:54

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)