您好,登錄后才能下訂單哦!
本篇內容介紹了“MySQL怎么恢復到任意一秒的狀態”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
看到這個題目是不是覺得數據庫再也不用擔心服務器 crash 了?
那我們需要學習為什么可以這么做?以及如何做?
即為什么可以恢復到任意時間點?如何恢復到任意時間點?
為什么有了 binlog 還需要 redo log?
事務是如何提交的?事務提交先寫 binlog 還是 redo log?如何保證這兩部分的日志做到順序一致性?
上一次課我們學習了一條 select
語句的全部執行過程,那么今天我們就從一條 update
語句開始。
mysql> update T set c=c+1 where ID=2;
其實執行流程和查詢流程一致,只是最后執行器執行的是找到這條數據,并進行更新。
另外,更新過程還涉及到一個重要的日志模塊,即 redo log
(重做日志)和 binlog
(歸檔日志)。
我個人是只聽過 binlog 的。
和大多數關系型數據庫一樣,InnoDB 記錄了對數據文件的物理更改,并保證總是日志先行。
也就是所謂的 WAL(Write-Ahead Logging),即在持久化數據文件前,保證之前的 redo log 已經寫到磁盤。
MySQL 的每一次更新并沒有每次都寫入磁盤,InnoDB 引擎會先將記錄寫到 redo log 里,并更新到內存中,然后再適當的時候,再把這個記錄更新到磁盤。
提到了兩個重要的日志,我覺得這里有必要貼一下 InnoDB 的存儲結構圖,對其有一個整體的認識:
如果下面看的各種空間懵逼了,建議回來看一眼這個圖。
當數據庫對數據做修改的時候,需要把數據頁從磁盤讀到 buffer pool 中,然后在 buffer pool 中進行修改。
那么這個時候 buffer pool 中的數據頁就與磁盤上的數據頁內容不一致,我們稱 buffer pool 的數據頁為 dirty page 臟數據。
感覺就像先拷貝一份數據,對拷貝的數據進行修改,修改完畢后再覆蓋到原數據。
這里也可以看出,所有的更新操作都是現在 dirty page
中進行的。
如果這個時候發生非正常的 DB 服務重啟,那么這些數據還沒在內存,并沒有同步到磁盤文件中(注意,同步到磁盤文件是個隨機 IO),也就是會發生數據丟失。
如果這個時候,能夠在有一個文件,當 buffer pool 中的 dirty page 變更結束后,把相應修改記錄記錄到這個文件(注意,記錄日志是順序 IO)。
那么當 DB 服務發生 crash 的情況,恢復 DB 的時候,也可以根據這個文件的記錄內容,重新應用到磁盤文件,數據保持一致。
這個文件就是 redo log ,用于記錄數據修改后的記錄,順序記錄。
我理解的,redo log 就是存放 dirty page 的物理空間。
在事務開始之后就產生 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 可能產生的速度非常快,導致頻繁的刷臟操作,進而導致性能下降。
如果可預期會有這樣的場景,我們建議調大 redo log 文件的大小。可以做一次干凈的 shutdown,然后修改 redo log 配置,重啟實例。
參考:
http://mysql.taobao.org/monthly/2015/05/01/
默認情況下,對應的物理文件位于數據庫的 data 目錄下的 ib_logfile1
、ib_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 中。
然后會通過以下三種方式將 innodb 日志緩沖區的日志刷新到磁盤:
1、Master Thread 每秒一次執行刷新 Innodb_log_buffer 到重做日志文件;
2、每個事務提交時會將重做日志刷新到重做日志文件;
3、當 redo log 緩存可用空間少于一半時,重做日志緩存被刷新到重做日志文件;
有了 redo log,InnoDB 就可以保證即使數據庫發生異常重啟,之前提交的記錄都不會丟失,這個能力稱為 crash-safe
。
CrashSafe 能夠保證 MySQL 服務器宕機重啟后:
所有已經提交的事務的數據仍然存在
所有沒有提交的事務的數據自動回滾
如前文所講,MySQL 整體可以分為 Server 層和引擎層。
其實,redo log 是屬于引擎層的 InnoDB 所特有的日志,而 Server 層也有自己的日志,即 binlog(歸檔日志)。
邏輯格式的日志,可以簡單認為就是執行過的事務中的 sql 語句。
但又不完全是 sql 語句這么簡單,而是包括了執行的 sql 語句(增刪改)反向的信息。
也就意味著 delete 對應著 delete 本身和其反向的 insert;update 對應著 update 執行前后的版本的信息;insert 對應著 delete 和 insert 本身的信息。
事務提交的時候,一次性將事務中的 sql 語句按照一定的格式記錄到 binlog 中。因此,對于較大事務的提交,可能會變得比較慢一些。
binlog 的默認是保持時間由參數 expire_logs_days
配置,也就是說對于非活動的日志文件,在生成時間超過配置的天數之后,會被自動刪除。
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
有了對這兩個日志的概念性理解,我們再來看執行器和 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),本次交易有效。
當需要恢復到指定的某一秒時,比如 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 始終保持一致。
我再來說下自己的理解
1、prepare 階段; 2、寫binlog 階段;3、commit 階段;
當在2之前崩潰時
重啟恢復:后發現沒有commit,回滾。
備份恢復:沒有 binlog 。一致。
當在3之前崩潰
重啟恢復:雖沒有commit,但滿足prepare和binlog完整,所以重啟后會自動commit。
備份:有binlog,一致。
總結起來說就是如果一個事務在 prepare 階段中落盤成功,并在 MySQL Server 層中的 binlog 也寫入成功,那這個事務必定 commit 成功。
介紹了 MySQL 里面最重要的兩個日志,即物理日志 redo log 和邏輯日志 binlog。
最好能夠理解這兩種日志的作用分別是什么,自己能夠理清楚事物的提交流程。
這次評論區精彩有點多!
問
課后題目
前面我說到定期全量備份的周期“取決于系統重要性,有的是一天一備,有的是一周一備”。那么在什么場景下,一天一備會比一周一備更有優勢呢?或者說,它影響了這個數據庫系統的哪個指標?
答1
備份時間周期的長短,感覺有2個方面
首先,是恢復數據丟失的時間,既然需要恢復,肯定是數據丟失了。如果一天一備份的話,只要找到這天的全備,加入這天某段時間的binlog來恢復,如果一周一備份,假設是周一,而你要恢復的數據是周日某個時間點,那就,需要全備+周一到周日某個時間點的全部binlog用來恢復,時間相比前者需要增加很多;看業務能忍受的程度
其次,是數據庫丟失,如果一周一備份的話,需要確保整個一周的binlog都完好無損,否則將無法恢復;而一天一備,只要保證這天的binlog都完好無損;當然這個可以通過校驗,或者冗余等技術來實現,相比之下,上面那點更重要
答2
備份數據庫的周期直接影響到了恢復的速度,一天一備的話,恢復時只需要重新執行最近一天的數據庫修改操作。而一周一備則需要做很多。所以在對于系統恢復速度很敏感的系統,最好使用一天一備,甚至一小時一備等等。
答3
我理解備份就是救命藥加后悔藥,災難發生的時候備份能救命,出現錯誤的時候備份能后悔。事情都有兩面性,沒有誰比誰好,只有誰比誰合適,完全看業務情況和需求而定。一天一備恢復時間更短,binlog更少,救命時候更快,但是后悔時間更短,而一周一備正好相反。我自己的備份策略是設置一個16小時延遲復制的從庫,充當后悔藥,恢復時間也較快。再兩天一個全備庫和binlog,作為救命藥,最后時刻用。這樣就比較兼顧了。
答4
1如果沒有主從,無Binlog Server,建議至少每天一備份,庫很小并發少,可以縮短備份周期,例如每小時備份一次。
2如果有主從,有Binlog Server,建議至少每周備份一次,庫較小并發不算高,可以縮短備份周期,例如每天備份一次。
這里不區分有主從無Binlog Server的情況,是由于重要系統建議至少搭建主從復制,盡可能搭建Binlog Server(金融環境尤為重要)。
問
1.首先客戶端通過tcp/ip發送一條sql語句到server層的SQL interface
2.SQL interface接到該請求后,先對該條語句進行解析,驗證權限是否匹配
3.驗證通過以后,分析器會對該語句分析,是否語法有錯誤等
4.接下來是優化器器生成相應的執行計劃,選擇最優的執行計劃
5.之后會是執行器根據執行計劃執行這條語句。在這一步會去open table,如果該table上有MDL,則等待。
如果沒有,則加在該表上加短暫的MDL(S)
(如果opend_table太大,表明open_table_cache太小。需要不停的去打開frm文件)
6.進入到引擎層,首先會去innodb_buffer_pool里的data dictionary(元數據信息)得到表信息
7.通過元數據信息,去lock info里查出是否會有相關的鎖信息,并把這條update語句需要的鎖信息寫入到lock info里(鎖這里還有待補充)
8.然后涉及到的老數據通過快照的方式存儲到innodb_buffer_pool里的undo page里,并且記錄undo log修改的redo
(如果data page里有就直接載入到undo page里,如果沒有,則需要去磁盤里取出相應page的數據,載入到undo page里)
9.在innodb_buffer_pool的data page做update操作。并把操作的物理數據頁修改記錄到redo log buffer里
由于update這個事務會涉及到多個頁面的修改,所以redo log buffer里會記錄多條頁面的修改信息。
因為group commit的原因,這次事務所產生的redo log buffer可能會跟隨其它事務一同flush并且sync到磁盤上
10.同時修改的信息,會按照event的格式,記錄到binlog_cache中。(這里注意binlog_cache_size是transaction級別的,不是session級別的參數,
一旦commit之后,dump線程會從binlog_cache里把event主動發送給slave的I/O線程)
11.之后把這條sql,需要在二級索引上做的修改,寫入到change buffer page,等到下次有其他sql需要讀取該二級索引時,再去與二級索引做merge
(隨機I/O變為順序I/O,但是由于現在的磁盤都是SSD,所以對于尋址來說,隨機I/O和順序I/O差距不大)
12.此時update語句已經完成,需要commit或者rollback。這里討論commit的情況,并且雙1
13.commit操作,由于存儲引擎層與server層之間采用的是內部XA(保證兩個事務的一致性,這里主要保證redo log和binlog的原子性),
所以提交分為prepare階段與commit階段
14.prepare階段,將事務的xid寫入,將binlog_cache里的進行flush以及sync操作(大事務的話這步非常耗時)
15.commit階段,由于之前該事務產生的redo log已經sync到磁盤了。所以這步只是在redo log里標記commit
16.當binlog和redo log都已經落盤以后,如果觸發了刷新臟頁的操作,先把該臟頁復制到doublewrite buffer里,把doublewrite buffer里的刷新到共享表空間,然后才是通過page cleaner線程把臟頁寫入到磁盤中
老師,你看我的步驟中有什么問題嘛?我感覺第6步那里有點問題,因為第5步已經去open table了,第6步還有沒有必要去buffer里查找元數據呢?這元數據是表示的系統的元數據嘛,還是所有表的?謝謝老師指正
答
其實在實現上5是調用了6的過程了的,所以是一回事。MySQL server 層和InnoDB層都保存了表結構,所以有書上描述時會拆開說。
這個描述很詳細,同時還有點到我們后面要講的內通
問
你好,關于提到的'數據頁'這個詞我沒有太理解,是一種存儲方式么?
答
MySQL的記錄是以“頁”為單位存取的,默認大小16K。也就是說,你要訪問磁盤中一個記錄,不會只讀這個記錄,而會把它所在的16K數據一起讀入內存
問
請問用redolog恢復時還寫binlog嗎?反之呢?
答
崩潰恢復過程不寫binlog了,用binlog恢復實例(或搭建備庫)的時候,是會寫redolog的
“MySQL怎么恢復到任意一秒的狀態”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。