MySQL是如何做到可以恢復到任意一秒狀態的

我們就從一條 update 語句開始。

mysql> update T set c=c+1 where ID=2;

其實執行流程和查詢流程一致,只是最后執行器執行的是找到這條數據,并進行更新。

另外,更新過程還涉及到一個重要的日志模塊,即 redo log(重做日志)和 binlog(歸檔日志)。

我個人是只聽過 binlog 的。

1 redo log

和大多數關系型數據庫一樣,InnoDB 記錄了對數據文件的物理更改,并保證總是日志先行

也就是所謂的 WAL(Write-Ahead Logging),即在持久化數據文件前,保證之前的 redo log 已經寫到磁盤。

MySQL 的每一次更新并沒有每次都寫入磁盤,InnoDB 引擎會先將記錄寫到 redo log 里,并更新到內存中,然后再適當的時候,再把這個記錄更新到磁盤。

提到了兩個重要的日志,我覺得這里有必要貼一下 InnoDB 的存儲結構圖,對其有一個整體的認識:

InnoDB 物理存儲結構

如果下面看的各種空間懵逼了,建議回來看一眼這個圖。

1.1 redo log 是啥?

當數據庫對數據做修改的時候,需要把數據頁從磁盤讀到 buffer pool 中,然后在 buffer pool 中進行修改。

那么這個時候 buffer pool 中的數據頁就與磁盤上的數據頁內容不一致,我們稱 buffer pool 的數據頁為 dirty page 臟數據

感覺就像先拷貝一份數據,對拷貝的數據進行修改,修改完畢后再覆蓋到原數據。

dirty page

這里也可以看出,所有的更新操作都是現在 dirty page 中進行的。

如果這個時候發生非正常的 DB 服務重啟,那么這些數據還沒在內存,并沒有同步到磁盤文件中(注意,同步到磁盤文件是個隨機 IO),也就是會發生數據丟失

如果這個時候,能夠在有一個文件,當 buffer pool 中的 dirty page 變更結束后,把相應修改記錄記錄到這個文件(注意,記錄日志是順序 IO)。

那么當 DB 服務發生 crash 的情況,恢復 DB 的時候,也可以根據這個文件的記錄內容,重新應用到磁盤文件,數據保持一致。

這個文件就是 redo log ,用于記錄數據修改后的記錄,順序記錄。

我理解的,redo log 就是存放 dirty page 的物理空間。

1.2 何時產生 & 釋放?

在事務開始之后就產生 redo log,redo log 的落盤并不是隨著事務的提交才寫入的,而是在事務的執行過程中,便開始寫入 redo log 文件中。

當對應事務的臟頁寫入到磁盤之后,redo log 的使命也就完成了,重做日志占用的空間就可以重用(被覆蓋)。

1.3 如何寫?

redo log 文件以 ib_logfile[number] 命名,并以順序的方式寫入文件文件,寫滿時則回溯到第一個文件,進行覆蓋寫。

循環寫

如圖所示:

  • write pos 是當前記錄的位置,一邊寫一邊后移,寫到最后一個文件末尾后就回到 0 號文件開頭;

  • checkpoint 是當前要擦除的位置,也是往后推移并且循環的,擦除記錄前要把記錄更新到數據文件;

write pos 和 checkpoint 之間還空著的部分,可以用來記錄新的操作。

如果 write pos 追上 checkpoint,表示寫滿,這時候不能再執行新的更新,得停下來先擦掉一些記錄,把 checkpoint 推進一下。

redo log 文件是循環寫入的,在覆蓋寫之前,總是要保證對應的臟頁已經刷到了磁盤。

在非常大的負載下,redo log 可能產生的速度非常快,導致頻繁的刷臟操作,進而導致性能下降。

如果可預期會有這樣的場景,我們建議調大 redo log 文件的大小。可以做一次干凈的 shutdown,然后修改 redo log 配置,重啟實例。

參考:
http://mysql.taobao.org/monthly/2015/05/01/

1.4 相關配置

默認情況下,對應的物理文件位于數據庫的 data 目錄下的 ib_logfile1ib_logfile2

innodb_log_group_home_dir 指定日志文件組所在的路徑,默認./ ,表示在數據庫的數據目錄下。innodb_log_files_in_group 指定重做日志文件組中文件的數量,默認2# 關于文件的大小和數量,由一下兩個參數配置innodb_log_file_size 重做日志文件的大小。innodb_mirrored_log_groups 指定了日志鏡像文件組的數量,默認1

1.5 其他

redo log 有一個緩存區 Innodb_log_buffer,默認大小為 8M,Innodb 存儲引擎先將重做日志寫入 innodb_log_buffer 中。

寫 redo log 過程

然后會通過以下三種方式將 innodb 日志緩沖區的日志刷新到磁盤:

1、Master Thread 每秒一次執行刷新 Innodb_log_buffer 到重做日志文件;
2、每個事務提交時會將重做日志刷新到重做日志文件;
3、當 redo log 緩存可用空間少于一半時,重做日志緩存被刷新到重做日志文件;

有了 redo log,InnoDB 就可以保證即使數據庫發生異常重啟,之前提交的記錄都不會丟失,這個能力稱為 crash-safe

CrashSafe 能夠保證 MySQL 服務器宕機重啟后:

  • 所有已經提交的事務的數據仍然存在

  • 所有沒有提交的事務的數據自動回滾


2 binlog

如前文所講,MySQL 整體可以分為 Server 層和引擎層。

其實,redo log 是屬于引擎層的 InnoDB 所特有的日志,而 Server 層也有自己的日志,即 binlog(歸檔日志)。

2.1 binlog 是什么?

邏輯格式的日志,可以簡單認為就是執行過的事務中的 sql 語句。

但又不完全是 sql 語句這么簡單,而是包括了執行的 sql 語句(增刪改)反向的信息。

也就意味著 delete 對應著 delete 本身和其反向的 insert;update 對應著 update 執行前后的版本的信息;insert 對應著 delete 和 insert 本身的信息。

2.2 何時產生 & 釋放?

事務提交的時候,一次性將事務中的 sql 語句按照一定的格式記錄到 binlog 中。因此,對于較大事務的提交,可能會變得比較慢一些。

binlog 的默認是保持時間由參數 expire_logs_days 配置,也就是說對于非活動的日志文件,在生成時間超過配置的天數之后,會被自動刪除。

2.3 和 redo log 的區別

1、redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 層實現,所有引擎都可以使用;

2、內容不同:redo log 是物理日志,記錄的是在數據頁上做了什么修改,是正在執行中的 dml 以及 ddl 語句;

而 binlog 是邏輯日志,記錄的是語句的原始邏輯,已經提交完畢之后的 dml 以及 ddl sql 語句,如「給 ID=2 的這一行的 c 字段加 1」;

3、寫方式不同:redo log 是循環寫的,空間固定;binlog 是可以一直追加寫的,一個文件寫到一定大小后,會繼續寫下一個,之前寫的文件不會被覆蓋;

4、作用不同:redo log 主要用來保證事務安全,作為異常 down 機或者介質故障后的數據恢復使用,binlog 主要用來做主從復制和即時點恢復時使用;

5、另外,兩者日志產生的時間,可以釋放的時間,在可釋放的情況下清理機制,都是完全不同的。

參考:
http://www.importnew.com/28039.html


3 數據更新事務流程

有了對這兩個日志的概念性理解,我們再來看執行器和 InnoDB 引擎在執行這個簡單的 update 語句時的內部流程。

1、執行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹搜索找到這一行。如果 ID=2 這一行所在的數據頁本來就在內存中,就直接返回給執行器;否則,需要先從磁盤讀入內存,然后再返回。

對應到上面講的,就是將數據加載到臟數據中。

2、執行器拿到引擎給的行數據,把這個值加上 1,比如原來是 N,現在就是 N+1,得到新的一行數據,再調用引擎接口寫入這行新數據。

3、引擎將這行新數據更新到內存中,同時將這個更新操作記錄到 redo log 里面,此時 redo log 處于 prepare 狀態。然后告知執行器執行完成了,隨時可以提交事務。

4、執行器生成這個操作的 binlog,并把 binlog 寫入磁盤

5、執行器調用引擎的提交事務接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態,更新完成。

事務流程

兩階段提交

上面處理 redo log 和 binlog 看著是不是有點懵逼?

其實這就是所謂的兩階段提交,即 COMMIT 會被自動的分成 prepare 和 commit 兩個階段。

兩階段提交

MySQL 在 prepare 階段會生成 xid,然后會在 commit 階段寫入到 binlog 中。在進行恢復時事務要提交還是回滾,是由 Binlog 來決定的。

由上面的二階段提交流程可以看出,通過兩階段提交方式保證了無論在任何情況下,事務要么同時存在于 redo log 和 binlog 中,要么兩個里面都不存在。

這樣就可以保證事務的 binlog 和 redo log 順序一致性。一旦階段 2 中持久化 binlog 完成,就確保了事務的提交。

此外需要注意的是,每個階段都需要進行一次 fsync 操作才能保證上下兩層數據的一致性。

PS:記錄 Binlog 是在 InnoDB 引擎 Prepare(即 Redo Log 寫入磁盤)之后,這點至關重要。

另外需要注意的一點就是,SQL 語句產生的 Redo 日志會一直刷新到磁盤(master thread 每秒 fsync redo log),而 Binlog 是事務 commit 時才刷新到磁盤,如果 binlog 太大則 commit 時會慢。

參考:
http://www.ywnds.com/?p=7892

舉個例子

Bin log 用于記錄了完整的邏輯記錄,所有的邏輯記錄在 bin log 里都能找到,所以在備份恢復時,是以 bin log 為基礎,通過其記錄的完整邏輯操作,備份出一個和原庫完整的數據。

如 redo log 執行了 update t set status = 1,此時原庫的數據 status 已更新為 1,而 bin log 寫入失敗,沒有記錄這一操作,后續備份恢復時,其 status = 0,導致數據不一致)。

其核心就是, redo log 記錄的,即使異常重啟,都會刷新到磁盤,而 bin log 記錄的, 則主要數據庫邏輯操作,主要用于備份恢復。

一個完整的交易過程:

賬本記上賣一瓶可樂(redo log 為 prepare 狀態),然后收錢放入錢箱(bin log 記錄)然后回過頭在賬本上打個勾(redo log 置為commit),給人可樂,表示一筆交易結束。

如果收錢時交易被打斷,回過頭來整理此次交易,發現只有記賬沒有收錢,則交易失敗,刪掉賬本上的記錄(回滾);

如果收了錢后被終止,然后回過頭發現賬本有記錄(prepare)而且錢箱有本次收入(bin log),則繼續完善賬本(commit),本次交易有效。

4 如何恢復任意時間的數據?

當需要恢復到指定的某一秒時,比如 2018.11.23 14.23.45 有一次數據庫誤操作,需要找回數據,那你可以這么做:

1、首先,找到最近的一次全量備份,如果你運氣好,可能就是昨天晚上 11.22 日的一個備份,從這個備份恢復到臨時庫;

2、然后,從備份的時間點開始,將備份的 binlog 依次取出來,重放到中午誤刪表之前的那個時刻。

這樣你的臨時庫就跟誤刪之前的線上庫一樣了,然后你可以把表數據從臨時庫取出來,按需要恢復到線上庫去。

當遇到 crash 時,恢復的過程也非常簡單:

1、恢復過程中會掃描最后一個 binlog 文件,提取其中的 xid;

2、重做檢查點以后的 redo 日志,搜集處于 prepare 階段的事務鏈表,將事務的 xid 與 binlog 中的 xid 對比。

若存在,說明事務記錄到 binlog 成功,但是最終未 commit 成功,則提交,否則就回滾;

這里要結合上面的兩段提交一起看,才能理解得比較透徹。

總結一下,基本頂多會出現下面是幾種情況:

  • 當事務在 prepare 階段 crash,數據庫 recovery 的時候該事務未寫入 binlog 并且 redo log 未提交,將該事務 rollback。

  • 當事務在 binlog 階段 crash,此時日志還沒有成功寫入到磁盤中,啟動時會 rollback 此事務。

  • 當事務在 binlog 日志已經 fsync 到磁盤后 crash,但是 InnoDB 沒有來得及 commit,此時 MySQL 數據庫 recovery 的時候將會讀出 binlog 中的 xid,然后告訴 InnoDB 提交這些 xid 的事務,InnoDB 提交完這些事務后會回滾其它的事務,使 redo log 和 binlog 始終保持一致。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容