MySQL系列之一條更新SQL的生命歷程

據說MySQL能恢復到半個月內任意一秒的狀態mysql

要從一條更新語句提及,若是將ID=2這一行的值+1,SQL語句能夠這樣寫:sql

mysql> update T set c=c+1 where ID=2;
複製代碼

執行一條更新語句一樣會走一遍查詢語句的流程:數據庫

  • 鏈接數據庫
  • 清空該表涉及到的緩存
  • 分析器經過詞法和語法解析出這是一條更新語句,並肯定涉及到表與字段
  • 優化器決定使用"ID"這個索引
  • 執行器找到這一行數據,而後進行更新操做
    與查詢過程不一樣的是,更新涉及到兩個日誌模塊:redo log(重作日誌)bin log(歸檔日誌)

臨時記錄:redo log

酒店掌櫃有一個帳本和一個小黑板,來作賒帳的記錄。有如下兩種方案:緩存

  1. 每一筆帳都打開帳本作記錄,當有人還帳時,找到對應的賒帳記錄,修改記錄的狀態
  2. 先在黑板上記錄本次要作的操做,打烊後按照黑板上的記錄向帳本上進行覈算

當生意紅火,顧客絡繹不絕時,第一種方案效率實在是低下,掌櫃的必定按照第二種方案來記帳。
一樣的,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日誌的區別:學習

  1. redo log 是屬於innoDB引擎全部,bin log是server提供的,全部引擎均可以使用
  2. redo log 屬於物理日誌,記錄"在某個數據頁作了什麼修改",bin log記錄的是該語句邏輯日誌"將ID=2的這一行c的值+1" [1]
  3. redo log日誌文件是循環使用的,空間有使用完的時刻,bin log是追加記錄的,不會覆蓋以前的記錄

也就是說,server搭配其餘引擎是沒有redo log的,所以也就沒有了crash-safe能力優化

更新具體流程

基於對兩個日誌文件的瞭解,再次深刻了解更新的流程spa

  1. 執行器先經過引擎使用樹搜索找到ID=2這一行,若是該記錄所在的數據頁自己就在內存中,則直接返回執行器,不然先從磁盤讀入內存,而後返回
  2. 執行器拿到數據後將c的值加一,而後經過引擎的寫入接口將修改後的數據寫入
  3. 引擎j將新數據更新到內存中,而後在redo log中記錄這次修改,這時redo log中該記錄的狀態置爲prepare,並告知執行器已經更新完成,隨時能夠提交事務
  4. 執行器生成這次操做的bin log,將bin log寫入磁盤中
  5. 執行器調用引擎的提交事務接口,引擎將剛剛寫入的redo log置爲commit狀態,更新結束

下圖是《MySQL實戰》提供的流程圖: 線程

淺色表明在innoDB中執行,深色在server中執行

兩階段提交

從上圖能夠看出,redo log是分兩個階段來提交的,這是爲了保持兩個日誌邏輯上一致 若是不用兩階段提交會發生什麼呢 利用反證法來看下:
假設初始ID=2的數據行,c的值爲0,如今要執行c+1的操做。

  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,與原庫中值不符合
  2. 先記錄bin log 後記錄redo log 寫完bin log就發生crash,還沒來得及寫入redo log,崩潰恢復後這個事務是無效的,所以c的值仍是0,可是bin log中已經記錄了"將c的值+1"的日誌,因此用bin log恢復出來的數據多出來一個事務,使得c的值爲1,與原庫中數據不符。
  3. 兩階段提交 記錄過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,因此與原庫中的值相同

總結以下:

兩種方式肯定記錄完整:

  1. redo log狀態爲 commit
  2. 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來恢復就靠不住的

  • @高枕 同窗的評論簡單精煉的表達了兩階段提交機制下的工做狀態:
    記錄日誌共有三個過程:

    1. prepare階段
    2. 寫binlog
    3. commit
    • 當在2以前崩潰時
      重啓恢復:後發現沒有commit,回滾。備份恢復:沒有binlog。備份與原庫一致
    • 當在3以前崩潰時
      重啓恢復:雖沒有commit,但知足prepare和binlog完整,因此重啓後會自動commit。備份:有binlog. 備份與原庫一致
  • 來自@黃金的太陽

    • 問:
    1. redo log自己也是文件,記錄文件的過程其實也是寫磁盤,那和文中提到的離線寫磁盤操做有何區別?
    2. 響應一次SQL我理解是要同時操做兩個日誌文件?也就是寫磁盤兩次?
    • 做者回復:
    1. 寫redo log是順序寫,不用去「找位置」,而更新數據須要找位置,所以redo log寫的速度更快

    2. 實際上是3次(redolog兩次 binlog 1次)。不過在併發更新的時候會合並寫

本文中含有極客時間《MySQL實戰》的圖和部分原文,若有侵權,請聯繫我會馬上刪除
第一節:ACID之I:事務隔離


  1. redo log不是記錄數據頁「更新以後的狀態」,而是記錄這個頁 「作了什麼改動」。
    bin log有兩種模式,statement 格式的話是記sql語句; row格式會記錄行的內容,記兩條,更新前和更新後都有。 ↩︎

相關文章
相關標籤/搜索