MySQL實戰 | 02-MySQL 如何恢復到半個月內任意一秒的狀態?

原文連接:MySQL是如何作到能夠恢復到任意一秒狀態的?html

看到這個題目是否是以爲數據庫不再用擔憂服務器 crash 了?mysql

那咱們須要學習爲何能夠這麼作?以及如何作?sql

即爲何能夠恢復到任意時間點?如何恢復到任意時間點?數據庫

爲何有了 binlog 還須要 redo log?緩存

事務是如何提交的?事務提交先寫 binlog 仍是 redo log?如何保證這兩部分的日誌作到順序一致性?安全

爲了保障主從複製安全,故障恢復是如何作的?服務器

<!--more-->性能

上一次課咱們學習了一條 select 語句的所有執行過程,那麼今天咱們就從一條 update 語句開始。學習

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

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

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

我我的是隻聽過 binlog 的。

redo log

和大多數關係型數據庫同樣,InnoDB 記錄了對數據文件的物理更改,並保證老是日誌先行

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

MySQL 的每一次更新並無每次都寫入磁盤,InnoDB 引擎會先將記錄寫到 redo log 裏,並更新到內存中,而後再適當的時候,再把這個記錄更新到磁盤。

這裏有必要貼一下 InnoDB 的存儲結構圖:

InnoDB 物理存儲結構

若是下面看的各類空間懵逼了,建議回來看一眼這個圖。

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 的物理空間。

log 什麼時候產生 & 釋放?

在事務開始以後就產生 redo log,redo log 的落盤並非隨着事務的提交才寫入的,而是在事務的執行過程當中,便開始寫入 redo log 文件中。

當對應事務的髒頁寫入到磁盤以後,redo log 的使命也就完成了,重作日誌佔用的空間就能夠重用(被覆蓋)。

如何寫?

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

循環寫

如圖所示:

  • write pos 是當前記錄的位置,一邊寫一邊後移,寫到最後一個文件末尾後就回到 0 號文件開頭;
  • checkpoint 是當前要擦除的位置,也是日後推移而且循環的,擦除記錄前要把記錄更新到數據文件;

write pos 和 checkpoint 之間還空着的部分,能夠用來記錄新的操做。

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

Redo log 文件是循環寫入的,在覆蓋寫以前,老是要保證對應的髒頁已經刷到了磁盤

在很是大的負載下,Redo log 可能產生的速度很是快,致使頻繁的刷髒操做,進而致使性能降低。

一般在未作 checkpoint 的日誌超過文件總大小的 76% 以後,InnoDB 認爲這多是個不安全的點,會強制的 preflush 髒頁,致使大量用戶線程 stall 住。

若是可預期會有這樣的場景,咱們建議調大 redo log 文件的大小。能夠作一次乾淨的 shutdown,而後修改 Redo log 配置,重啓實例。

參考:
http://mysql.taobao.org/month...

相關配置

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

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

其餘

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

寫 redo log 過程

而後會經過如下三種方式將 innodb 日誌緩衝區的日誌刷新到磁盤:

一、Master Thread 每秒一次執行刷新 Innodb_log_buffer 到重作日誌文件;
二、每一個事務提交時會將重作日誌刷新到重作日誌文件;
三、當 redo log 緩存可用空間少於一半時,重作日誌緩存被刷新到重作日誌文件;

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

CrashSafe 可以保證 MySQL 服務器宕機重啓後:

  • 全部已經提交的事務的數據仍然存在。
  • 全部沒有提交的事務的數據自動回滾。

binlog

如前文所講,MySQL 總體能夠分爲 Server 層和引擎層。

其實,redo log 是屬於引擎層的 InnoDB 所特有的日誌,而 Server 層也有本身的日誌,即 binlog(歸檔日誌)。

記錄了什麼

邏輯格式的日誌,能夠簡單認爲就是執行過的事務中的 sql 語句。

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

也就意味着 delete 對應着 delete 自己和其反向的 insert;update 對應着 update 執行先後的版本的信息;insert 對應着 delete 和 insert 自己的信息。

什麼時候產生 & 釋放

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

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

區別

一、redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 層實現,全部引擎均可以使用;
二、內容不一樣:redo log 是物理日誌,記錄的是在數據頁上作了什麼修改,是正在執行中的 dml 以及 ddl 語句;而 binlog 是邏輯日誌,記錄的是語句的原始邏輯,已經提交完畢以後的 dml 以及 ddl sql 語句,如「給 ID=2 的這一行的 c 字段加 1」;
三、寫方式不一樣:redo log 是循環寫的,空間固定;binlog 是能夠一直追加寫的,一個文件寫到必定大小後,會繼續寫下一個,以前寫的文件不會被覆蓋;
四、做用不一樣:redo log 主要用來保證事務安全,做爲異常 down 機或者介質故障後的數據恢復使用,binlog 主要用來作主從複製和即時點恢復時使用;
五、另外,二者日誌產生的時間,能夠釋放的時間,在可釋放的狀況下清理機制,都是徹底不一樣的。

參考:
http://www.importnew.com/2803...


數據更新事務流程

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

一、執行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹搜索找到這一行。若是 ID=2 這一行所在的數據頁原本就在內存中,就直接返回給執行器;不然,須要先從磁盤讀入內存,而後再返回。

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

三、引擎將這行新數據更新到內存中,同時將這個更新操做記錄到 redo log 裏面,此時 redo log 處於 prepare 狀態。而後告知執行器執行完成了,隨時能夠提交事務。

四、執行器生成這個操做的 binlog,並把 binlog 寫入磁盤

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

事務流程

兩階段提交

上面處理 redo log 和 binlog 看着是否是有點懵逼?

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

兩階段提交

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

由上面的二階段提交流程能夠看出,經過兩階段提交方式保證了不管在任何狀況下,事務要麼同時存在於存儲引擎和 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

如何恢復數據?

當須要恢復到指定的某一秒時,好比某天下午兩點發現中午十二點有一次誤刪表,須要找回數據,那你能夠這麼作:

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

二、而後,從備份的時間點開始,將備份的 binlog 依次取出來,重放到中午誤刪表以前的那個時刻。

這樣你的臨時庫就跟誤刪以前的線上庫同樣了,而後你能夠把表數據從臨時庫取出來,按須要恢復到線上庫去。

當遇到 crash 時,恢復的過程也很是簡單:

一、掃描最後一個 Binlog 文件,提取其中的 xid;
二、重作檢查點之後的 redo 日誌,蒐集處於 prepare 階段的事務鏈表,將事務的 xid 與 binlog 中的 xid 對比,若存在,則提交,不然就回滾;

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

  • 當事務在 prepare 階段 crash,數據庫 recovery 的時候該事務未寫入 Binary log 而且存儲引擎未提交,將該事務 rollback。
  • 當事務在 binlog 階段 crash,此時日誌尚未成功寫入到磁盤中,啓動時會 rollback 此事務。
  • 當事務在 binlog 日誌已經 fsync 到磁盤後 crash,可是 InnoDB 沒有來得及 commit,此時 MySQL 數據庫 recovery 的時候將會讀出 binlog 中的 xid,而後告訴 InnoDB 提交這些 xid 的事務,InnoDB 提交完這些事務後會回滾其它的事務,使存儲引擎和二進制日誌始終保持一致。

總結起來講就是若是一個事務在 prepare 階段中落盤成功,並在 MySQL Server 層中的 binlog 也寫入成功,那這個事務一定 commit 成功。

總結

介紹了 MySQL 裏面最重要的兩個日誌,即物理日誌 redo log 和邏輯日誌 binlog。

redo log 用於保證 crash-safe 能力。innodb_flush_log_at_trx_commit 這個參數設置成 1 的時候,表示每次事務的 redo log 都直接持久化到磁盤。這個參數我建議你設置成 1,這樣能夠保證 MySQL 異常重啓以後數據不丟失。

sync_binlog 這個參數設置成 1 的時候,表示每次事務的 binlog 都持久化到磁盤。這個參數我也建議你設置成 1,這樣能夠保證 MySQL 異常重啓以後 binlog 不丟失。

我還跟你介紹了與 MySQL 日誌系統密切相關的「兩階段提交」。兩階段提交是跨系統維持數據邏輯一致性時經常使用的一個方案,即便你不作數據庫內核開發,平常開發中也有可能會用到。

你的關注是對我最大的鼓勵!

最近蒐集到傳智播客 2018 最新 Python 和 Java 教程!關注本公衆號,後臺回覆「2018」便可獲取下載地址。

公衆號提供CSDN資源免費下載服務!

相關文章
相關標籤/搜索