事情是這樣的,我負責我司的報表系統,小胖是我小弟。某天他手賤誤刪了一條生產的數據。被用戶在羣裏瘋狂投訴質問,火急火燎的跑來問我怎麼辦。我特麼冷汗都出來了,訓斥了他一頓:蠢,蠢得均可以進博物館了,生產的數據能隨便動?前端
小胖看我日常笑嘻嘻的,沒想到發這麼大的火。心一急,竟然給我跪下了:遠哥,我上有老,下有小,中有女友,不要開除我呀。我一聽火更大了:合着就你有女友???這個時候咱們 DBA 老林來打圓場:別慌,年輕人管不住下自己,不免作錯事。我能夠把數據恢復到一個月內任意時刻的狀態。聽到這,小胖忙抱着老林大腿哭爹喊娘地感謝。java
聽到這你是否是很奇怪?能恢復到半個月前的數據?DBA 老林究竟是如何作到的?我跟他細聊了一番。老林點燃了手中 82 年的華子,深深吸了一口說到:事情還得從 update 語句是如何執行的提及。mysql
假設我如今有建表語句,以下:面試
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 66 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
複製代碼
表數據以下:算法
今天剛好張三生日,我要把它的 age 加一歲。因而執行如下的 sql 語句:sql
update student set age = age + 1 where id = 2;
複製代碼
前面聊過查詢語句是如何執行的?錯過的同窗看這篇《工做三年:小胖連 select 語句是如何執行的都不知道,真的菜!》,裏面的查詢語句流程,更新語句也會走一遍,以下流程圖:數據庫
update 語句發起:首先鏈接器會鏈接數據庫。接着分析器經過詞法、語法分析知道這是更新語句。因此查詢緩存失效。編程
以前的文章提到:若是表有更新。那麼它的查詢緩存會失敗。這也是爲啥,我不建議你使用查詢緩存的緣由。設計模式
優化器則決定使用 ID 索引去更新,最後執行器負責找到這行數據,執行更新。緩存
重點來了:與查詢流程不同,更新還涉及兩個重要的日誌模塊。一是重作日誌: redo log,二是歸檔日誌:binlog。要解答文章開頭的問題,必需要明白這兩日誌的原理才能整明白 DBA 是怎麼作到的。
什麼是 redo log?爲了方便理解,先舉個來自極客時間的例子:
還記得《孔乙己》這篇文章,飯店掌櫃有一個粉板,專門用來記錄客人的賒帳記錄。若是賒帳的人很少,那麼他能夠把顧客名和帳目寫在板上。但若是賒帳的人多了,粉板總會有記不下的時候,這個時候掌櫃必定還有一個專門記錄賒帳的帳本。
若是有人要賒帳或者還帳的話,掌櫃通常有兩種作法:
一種作法是直接把帳本翻出來,把此次賒的帳加上去或者扣除掉;
另外一種作法是先在粉板上記下此次的帳,等打烊之後再把帳本翻出來覈算。
在生意紅火櫃檯很忙時,掌櫃必定會選擇後者,由於前者操做實在是太麻煩了。首先,你得找到這我的的賒帳總額那條記錄。你想一想,密密麻麻幾十頁,掌櫃要找到那個名字,可能還得帶上老花鏡慢慢找,找到以後再拿出算盤計算,最後再將結果寫回到帳本上。
這整個過程想一想都麻煩。相比之下,仍是先在粉板上記一下方便。你想一想,若是掌櫃沒有粉板的幫助,每次記帳都得翻帳本,效率是否是低得讓人難以忍受?
一樣,在 MySQL 中,若是每一次的更新要寫進磁盤,這麼作會帶來嚴重的性能問題:
爲了解決這個問題,MySQL 的設計者就用了相似掌櫃粉板的思路來提高更新效率。這種思路在 MySQL 中叫 WAL(Write-Ahead Logging),意思就是:先寫 redo log 日誌,後寫磁盤。日誌和磁盤就對應上面的粉板和帳本。
具體到 MySQL 是這樣的:有記錄須要更新,InnDB 把記錄寫到 redo log 中,並更新內存中的數據頁,此時更新就算完成。同時,後臺線程會把操做記錄更新異步到磁盤中的數據頁。
PS:當須要更新的數據頁在內存中時,就會直接更新內存中的數據頁;不在內存中時,在可使用 change buffer(篇幅有限,這個後面寫文章再聊) 的狀況下,就會將更新操做記錄到 change buffer 中,並將這些操做記錄到 redo log 中;若是此時有查詢操做,則觸發 merge 操做,返回更改後的記錄值。
有些人說 InnoDB 引擎把日誌記錄寫到 redo log 中,redo log 在哪,不也是在磁盤上麼?
對,這也是一個寫磁盤的過程,可是與更新過程不同的是,更新過程是在磁盤上隨機 IO,費時。 而寫 redo log 是在磁盤上順序 IO,效率要高。
PPS:redo log 的存在就是把全局的隨機寫,變換爲局部的順序寫,從而提升效率。
redo log 記錄了事務對數據頁作了哪些修改。它包括兩部分:分別是內存中的日誌緩衝(redo log buffer)和磁盤上的日誌文件(redo logfile)。
mysql 每執行一條 DML 語句,先將記錄寫入 redo log buffer,後續某個時間點再一次性將多個操做記錄寫到 redo log file。也就是咱們上面提到的 WAL 技術。
計算機操做系統告訴咱們:用戶空間下的緩衝區數據是沒法直接寫入磁盤的。由於中間必須通過操做系統的內核空間緩衝區(OS Buffer)。
因此,redo log buffer 寫入 redo logfile 其實是先寫入 OS Buffer,而後操做系統調用 fsync() 函數將日誌刷到磁盤。過程以下:
mysql 支持三種將 redo log buffer 寫入 redo log file 的時機,能夠經過 innodb_flush_log_at_trx_commit 參數配置,各參數值含義以下:建議設置成1,這樣能夠保證MySQL 異常重啓以後數據不丟失。
參數值 | 含義 |
---|---|
0(延遲寫) | 事務提交時不會將 redo log buffer 中日誌寫到 os buffer,而是每秒寫入os buffer 並調用 fsync() 寫入到 redo logfile 中。也就是說設置爲 0 時是(大約)每秒刷新寫入到磁盤中的,當系統崩潰,會丟失1秒鐘的數據。 |
1(實時寫、實時刷新) | 事務每次提交都會將 redo log buffer 中的日誌寫入 os buffer 並調用 fsync() 刷到 redo logfile 中。這種方式即便系統崩潰也不會丟失任何數據,可是由於每次提交都寫入磁盤,IO的性能差。 |
2(實時寫、延遲刷新刷新) | 每次提交都僅寫入到 os buffer,而後是每秒調用 fsync() 將 os buffer 中的日誌寫入到 redo log file。 |
寫的過程以下:
InnoDB 的 redo log 是固定大小的。好比能夠配置爲一組 4 個文件,每一個文件的大小是 1GB,那麼 redo log file 能夠記錄 4GB 的操做。從頭開始寫。寫到末尾又回到開頭循環寫。以下圖:
上圖中,write pos 表示 redo log 當前記錄的 LSN (邏輯序列號) 位置,一邊寫一遍後移,寫到第 3 號文件末尾後就回到 0 號文件開頭; check point 表示數據頁更改記錄刷盤後對應 redo log 所處的 LSN(邏輯序列號) 位置,也是日後推移而且循環的。
PS:check point 是當前要擦除的位置,它與數據頁中的 LSN 應當是一致的。
write pos 到 check point 之間的部分是 redo log 的未寫區域,可用於記錄新的記錄;check point 到 write pos 之間是 redo log 已寫區域,是待刷盤的數據頁更改記錄。
當 write pos 追上 check point 時,表示 redo log file 寫滿了,這時候有就不能執行新的更新。得停下來先擦除一些記錄(擦除前要先把記錄刷盤),再推進 check point 向前移動,騰出位置再記錄新的日誌。
有了 redo log ,即在 InnoDB 存儲引擎中,事務提交過程當中任何階段,MySQL 忽然奔潰,重啓後都能保證事務的完整性,已提交的數據不會丟失,未提交完整的數據會自動進行回滾。這個能力稱爲 crash-safe,依賴的就是 redo log 和 undo log 兩個日誌。
好比:重啓 innodb 時,首先會檢查磁盤中數據頁的 LSN ,若是數據頁的 LSN 小於日誌中 check point 的 LSN ,則會從 checkpoint 開始恢復。
**undo log,主要提供回滾的做用,但還有另外一個做用,就是多個行版本控制 (MVCC),保證事務的原子性。**在數據修改的流程中,會記錄一條與當前操做相反的邏輯日誌到 undo log 中(能夠認爲當 delete 一條記錄時,undo log 中會記錄一條對應的 insert 記錄,反之亦然,當 update 一條記錄時,它記錄一條對應相反的 update 記錄),若是由於某些緣由致使事務異常失敗了,能夠藉助該 undo log 進行回滾,保證事務的完整性,因此 undo log 也必不可少。
上一篇聊查詢語句的執行過程時,聊到 MySQL 的架構包含 server 層和引擎層。而 redo log 是 InnoDB 引擎特有的日誌,而 server 層也有本身的日誌,那就是 binlog。
最開始 MySQL 裏並無 InnoDB 引擎。MySQ L自帶的引擎是 MyISAM,可是 MyISAM 沒有 crash-safe 的能力,binlog 日誌只能用於歸檔。而 InnoDB 是另外一個公司以插件形式引入MySQL 的,只依靠 binlog 是沒有 crash-safe 能力的,因此 InnoDB 使用另一套日誌系統——也就是 redo log 來實現 crash-safe 能力。
binlog 有三種格式,分別爲 STATMENT 、 ROW 和 MIXED。
在 MySQL 5.7.7 以前,默認的格式是 STATEMENT , MySQL 5.7.7 以後,默認值是 ROW。日誌格式經過 binlog-format 指定。
只用一個 binlog 是否能夠實現 cash_safe 能力呢?答案是能夠的,只不過 binlog 中也要加入 checkpoint,數據庫故障重啓後,binlog checkpoint 以後的 sql 都重放一遍。可是這樣作讓 binlog 耦合的功能太多。
有人說,也能夠直接直接對比匹配全量 binlog 和磁盤數據庫文件,但這樣作的話,效率低不說。由於 binlog 是 server 層的記錄並非引擎層的,有可能致使數據不一致的狀況:
假如 binlog 記錄了 3 條數據,正常狀況引擎層也寫了 3 條數據,可是此時節點宕機重啓,binlog 發現有 3 條記錄須要回放,因此回放 3 條記錄,可是引擎層可能已經寫入了 2 條數據到磁盤,只須要回放一條 1 數據。那 binlog 回放的前兩條數據會不會重複呢,好比會報錯 duplicate key。
另外,binlog 是追加寫,crash 時不能斷定 binlog 中哪些內容是已經寫入到磁盤,哪些還沒被寫入。而 redolog 是循環寫,從 check point 到 write pos 間的內容都是未寫入到磁盤的。
因此,binlog 並不適合作 crash-save。
redo log 和 binlog 主要有三種不一樣:
瞭解了兩種日誌的概念,再來看看執行器和 InnoDB 引擎在執行 update 語句時的流程:
整個過程以下圖所示,其中橙色框表示是在 InnoDB 內部執行的,綠色框表示是在執行器中執行的:
因爲 redo log 和 binlog 是兩個獨立的邏輯,若是不用兩階段提交,要麼就是先寫完 redo log 再寫 binlog,或者採用反過來的順序。咱們看看這兩種方式會有什麼問題。
仍然用前面的 update 語句來作例子。假設當前 id=2 的行,字段 age 的值是 22,再假設執行update 語句過程當中在寫完第一個日誌後,第二個日誌尚未寫完期間發生了 crash,會出現什麼狀況呢?
可是 binlog 沒寫完就 crash 了,這時 binlog 裏面並無記錄這個語句。所以,以後備份日誌的時候,存起來的 binlog 裏面就沒有這條語句。
等到須要用這個binlog 來恢復臨時庫的話,因爲這個語句的 binlog 丟失,這個臨時庫就會少了這一次更新,恢復出來的這一行 age 值就是 22,與原庫的值不一樣。
因此,若是不使用"兩階段提交",數據庫的狀態就有可能和用 binlog 恢復出來的不一致。
另外:sync_binlog 這個參數建議設置成 1,表示每次事務的binlog都持久化到磁盤,這樣能夠保證 MySQL 異常重啓以後 binlog 不丟失。
主從複製 :在 Master 端開啓 binlog ,而後將 binlog 發送到各個 Slave 端, Slave 端重放 binlog 從而達到主從數據一致。
數據恢復 :經過使用 mysqlbinlog 工具來恢復數據。
前面說過,binlog 會記錄全部的邏輯操做,而且是採用"追加寫"的形式。若是你的DBA承諾說一個月內能夠恢復,那麼備份系統中必定會保存最近一個月的全部 binlog,同時系統會按期作整庫備份。這裏的"按期"取決於系統的重要性,能夠是一天一備,也能夠是一週一備。
當須要恢復到指定的某一秒時,好比某天下午兩點發現中午十二點有一次誤刪表,須要找回數據,那你能夠這麼作:
這樣你的臨時庫就跟誤刪以前的線上庫同樣了,而後你能夠把表數據從臨時庫取出來,按須要恢復到線上庫。
看到這裏,小胖露出了目視父親的笑容。
巨人的肩膀
本文講解了事務日誌(redo og)的幾個方面:爲何須要 redo log?它的寫入過程、結構、存的啥以及什麼是 crash-save等等;此外還聊了 binlog 的定義、日誌格式、與 redo log 的區別、update 語句的執行流程、兩階段提交、以及 binlog 的應用場景。
好啦,以上就是狗哥關於 MySQL 日誌的總結。感謝各技術社區大佬們的付出,若是說我看得更遠,那是由於我站在大家的肩膀上。但願這篇文章對你有幫助,咱們下篇文章見~
若是看到這裏,喜歡這篇文章的話,請幫點個好看。微信搜索JavaFish,關注後回覆電子書送你 1000+ 本編程電子書 ,包括 C、C++、Java、Python、GO、Linux、Git、數據庫、設計模式、前端、人工智能、面試相關、數據結構與算法以及計算機基礎,詳情看下圖。回覆 1024送你一套完整的 java 視頻教程。