據說MySQL能恢復到半個月內任意一秒的狀態mysql
要從一條更新語句提及,若是將ID=2這一行的值+1,SQL語句能夠這樣寫:sql
mysql> update T set c=c+1 where ID=2;
複製代碼
執行一條更新語句一樣會走一遍查詢語句的流程:數據庫
- 鏈接數據庫
- 清空該表涉及到的緩存
- 分析器經過詞法和語法解析出這是一條更新語句,並肯定涉及到表與字段
- 優化器決定使用
"ID"
這個索引
- 執行器找到這一行數據,而後進行更新操做
與查詢過程不一樣的是,更新涉及到兩個日誌模塊:redo log(重作日誌)和bin log(歸檔日誌)
臨時記錄:redo log
酒店掌櫃有一個帳本和一個小黑板,來作賒帳的記錄。有如下兩種方案:緩存
- 每一筆帳都打開帳本作記錄,當有人還帳時,找到對應的賒帳記錄,修改記錄的狀態
- 先在黑板上記錄本次要作的操做,打烊後按照黑板上的記錄向帳本上進行覈算
當生意紅火,顧客絡繹不絕時,第一種方案效率實在是低下,掌櫃的必定按照第二種方案來記帳。
一樣的,MySQL若是每次更新操做都要寫入磁盤,在磁盤中找到對應記錄,而後更新,這個過程的IO成本、查找成本都過高了。
爲了解決和這個問題,MySQL就使用了相似於黑板-帳本
模式來提升效率。這一模式即爲WAL技術
,全程爲Write-Ahead Logging
,關鍵點:先寫日誌,再寫磁盤,也就是前文中先寫黑板,再寫帳本。併發
具體步驟以下:
當有記錄須要更新,innoDB先把記錄寫入redo log中,並更新內存,這是更新操做就算結束了。innoDB引擎會在適當的時候講操做記錄更新到磁盤裏,這一動做通常是系統比較閒的時候作的。
redo log的大小是固定的,共有4個文件組成,每一個大小爲1G。邏輯上能夠將4個文件理解爲環形,從頭開始寫,寫到末尾又從新開始新的一輪,以下圖所示 post
write pos爲當前記錄位置,check point爲當前擦除點的位置,當記錄更新時,check point會隨着文件的記錄向後移動。擦除後未寫入的位置能夠記錄新的操做。當write pos追上了check point,則須要停下來寫入動做,將redo log內容寫入磁盤,而後清除check point向後移動。
有了redo log,innoDB能夠知曉每一次操做,保證當數據庫發生異常重啓時,以前的可以根據redo log恢復以前的記錄,這種能力叫作
"crash-safe"
。
歸檔日誌:bin log
redo log與bin log日誌的區別:學習
- redo log 是屬於innoDB引擎全部,bin log是server提供的,全部引擎均可以使用
- redo log 屬於物理日誌,記錄
"在某個數據頁作了什麼修改"
,bin log記錄的是該語句邏輯日誌"將ID=2的這一行c的值+1"
- redo log日誌文件是循環使用的,空間有使用完的時刻,bin log是追加記錄的,不會覆蓋以前的記錄
也就是說,server搭配其餘引擎是沒有redo log的,所以也就沒有了crash-safe能力優化
更新具體流程
基於對兩個日誌文件的瞭解,再次深刻了解更新的流程spa
- 執行器先經過引擎使用樹搜索找到ID=2這一行,若是該記錄所在的數據頁自己就在內存中,則直接返回執行器,不然先從磁盤讀入內存,而後返回
- 執行器拿到數據後將c的值加一,而後經過引擎的寫入接口將修改後的數據寫入
- 引擎j將新數據更新到內存中,而後在redo log中記錄這次修改,這時redo log中該記錄的狀態置爲prepare,並告知執行器已經更新完成,隨時能夠提交事務
- 執行器生成這次操做的bin log,將bin log寫入磁盤中
- 執行器調用引擎的提交事務接口,引擎將剛剛寫入的redo log置爲commit狀態,更新結束
下圖是《MySQL實戰》提供的流程圖: 線程
淺色表明在innoDB中執行,深色在server中執行
兩階段提交
從上圖能夠看出,redo log是分兩個階段來提交的,這是爲了保持兩個日誌邏輯上一致 若是不用兩階段提交會發生什麼呢 利用反證法來看下:
假設初始ID=2的數據行,c的值爲0,如今要執行c+1的操做。
- 先記錄redo log 後記錄bin log 若是剛記錄完redo log,尚未記錄bin log時,c的值已經記錄變爲1,這時MySQL服務崩潰重啓,根據crash-safe機制,能夠用redo log來恢復數據庫,恢復後的數據中c的值爲1。因爲bin log中沒有記錄這一變化,之後備份bin log時,c的值仍是0。若是有一天須要從bin log回覆一臺備用數據庫,因爲bin log少了一次更新,則最後恢復出來的c值仍然爲0,與原庫中值不符合
- 先記錄bin log 後記錄redo log 寫完bin log就發生crash,還沒來得及寫入redo log,崩潰恢復後這個事務是無效的,所以c的值仍是0,可是bin log中已經記錄了"將c的值+1"的日誌,因此用bin log恢復出來的數據多出來一個事務,使得c的值爲1,與原庫中數據不符。
- 兩階段提交 記錄過bin log回過頭提交commit(可參見評論區知識點) 更新redo log後,尚未記錄bin log時崩潰,這時redo log的狀態仍是prepare,事務並無提交,並且bin log中沒有記錄,所以因爲crash-safe機制,並不會恢復該記錄,c的值仍然爲0,因爲bin log中沒有記錄,之後從bin log恢復數據時,c的值在此操做中並無記錄變化,所以仍是0,與原庫中數據一致;另外一種狀況:更新redo log,也更新了bin log,下一步執行器調用commit接口前崩潰,這時雖然redo log中狀態爲prepare,可是從bin log中查到有記錄,因此仍是會從redo log中恢復c=1,後面直接從bin log恢復出新的數據庫時,由於已經記錄c的值+1,因此與原庫中的值相同
總結以下:
兩種方式肯定記錄完整:
- redo log狀態爲 commit
- redo log狀態爲prepare而且bin log記錄完整 (提交commit以前)
總結
這節主要學習了兩個日誌文件的用法 redo log 用於保證 crash-safe 能力,bin log用於恢復數據的完整性
- innodb_flush_log_at_trx_commit 這個參數設置成 1 的時候,表示每次事務的 redo log 都直接持久化到磁盤。這樣能夠保證 MySQL 異常重啓以後數據不丟失。
- sync_binlog 這個參數設置成 1 的時候,表示每次事務的 binlog 都持久化到磁盤。這樣能夠保證 MySQL 異常重啓以後 binlog 不丟失。
評論區知識點:
-
binlog沒有被用來作崩潰恢復,binlog是能夠關的,你若是有權限,能夠"set sql_log_bin=0"
關掉本線程的binlog日誌。 因此只依賴binlog來恢復就靠不住的
-
@高枕 同窗的評論簡單精煉的表達了兩階段提交機制下的工做狀態:
記錄日誌共有三個過程:
- prepare階段
- 寫binlog
- commit
- 當在2以前崩潰時
重啓恢復:後發現沒有commit,回滾。備份恢復:沒有binlog。備份與原庫一致
- 當在3以前崩潰時
重啓恢復:雖沒有commit,但知足prepare和binlog完整,因此重啓後會自動commit。備份:有binlog. 備份與原庫一致
-
來自@黃金的太陽
- redo log自己也是文件,記錄文件的過程其實也是寫磁盤,那和文中提到的離線寫磁盤操做有何區別?
- 響應一次SQL我理解是要同時操做兩個日誌文件?也就是寫磁盤兩次?
-
寫redo log是順序寫,不用去「找位置」,而更新數據須要找位置,所以redo log寫的速度更快
-
實際上是3次(redolog兩次 binlog 1次)。不過在併發更新的時候會合並寫
本文中含有極客時間《MySQL實戰》的圖和部分原文,若有侵權,請聯繫我會馬上刪除
第一節:ACID之I:事務隔離