1. 程式人生 > 實用技巧 >MySql日誌系統

MySql日誌系統

MySQL日誌系統,主要包括redo log(重做日誌)、undo log(回滾日誌)和binlog(歸檔日誌)。回滾日誌主要用來一致性檢視中實現MVCC,所以undo log在學習事務時再介紹。

假設現在有一張表,建表語句如下:

mysql> create table T(ID int primary key, c int);

我們再來看一個簡單的更新語句:

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

MySQL的內部邏輯架構圖如下圖所示:

查詢語句和更新語句執行的內部邏輯基本上都是一樣的,但是更新語句涉及到了資料的更改,所以需要引入日誌模組,即redo log重做日誌模組和binlog歸檔日誌模組。接下來,我們依次介紹這兩個重要的日誌模組。

一、redo log (重做日誌模組)

1、磁碟IO和記憶體讀寫效率的關係

在介紹redo log之前,我們先來了解下磁碟和記憶體的區別與聯絡。MySQL是一種關係型資料庫,資料持久化儲存在磁碟上。磁碟的IO效率很低,與之對應的是記憶體的IO效率遠高於磁碟IO。在MySQL中,如果每一次的更新操作都需要寫進磁碟,然後磁碟也要找到對應的那條記錄,然後再更新,整個過程 IO 成本、查詢成本都很高。為了解決這個問題,MySQL採用了另外一種更新機制,先更新儲存在記憶體中,寫入記憶體成功,即表示該資料更新成功,給客戶端返回。隨後,在一個數據庫空閒的時間段或者是記憶體佔滿之後,將記憶體中的資料刷到磁碟上。

2、WAL(Write-Ahead Logging技術)

這麼做有什麼問題嗎?當然有問題,如果發生異常重啟的現象,那麼記憶體中的資料將會丟失,出現數據不一致的情況。那麼如何解決呢?這個時候,我們的redo log重做日誌就該閃亮登場了。在更新資料寫入記憶體的同時,我們會記錄redo log,並且持久化到磁碟,當資料庫異常宕機之後,我們可以根據redo log重做日誌來恢復資料,保證之前提交的資料不會丟失,也就是擁有了crash-safe的能力。同時這就是我們常說的WAL技術,即Write-Ahead Logging,它的關鍵點就是先寫日誌,再寫磁碟。

3、redo log示意圖:

InnoDB 的 redo log 是固定大小的,比如可以配置為一組 4 個檔案,每個檔案的大小是 1GB,那麼總共就可以記錄 4GB 的操作。從頭開始寫,寫到末尾就又回到開頭迴圈寫,如下面這個圖所示:

write pos 是當前記錄的位置,一邊寫一邊後移,寫到第 3 號檔案末尾後就回到 0 號檔案開頭。checkpoint 是當前要擦除的位置,也是往後推移並且迴圈的,擦除記錄前要把記錄更新到資料檔案。write pos 和 checkpoint 之間的是redo log還空著的部分,可以用來記錄新的操作。如果 write pos 追上 checkpoint,表示redo log滿了,這時候不能再執行新的更新,得停下來先擦掉一些記錄,把 checkpoint 推進一下。有了 redo log,InnoDB 就可以保證即使資料庫發生異常重啟,之前提交的記錄都不會丟失,這個能力稱為crash-safe。

4、redo log儲存的資料結構如下所示:

  • reod_log_type: 佔用1位元組,表示重做日誌型別。各種不同操作有不同的重做日誌格式,但有基本的格式

  • space:表空間的ID,採用壓縮的方式,佔用空間可能小於4位元組

  • page_no:頁的偏移量,同樣採用壓縮方式

  • redo_log_body:每個重做日誌的資料部分,恢復時需要呼叫相應的函式解析。

5、為什麼redo log寫入效率比直接寫磁碟效率高?

redo log是一種日誌檔案,必然也是持久化到磁碟上的。那為什麼先更新記憶體,然後寫redo log會比資料直接寫磁碟效率高呢?原因在於redo log可以迴圈並且順序寫入磁碟,資料直接寫入磁碟多了查詢源資料和隨機寫入的過程,自然效率會低下很多。

6、redo log和buffer pool的關係:

更新資料其實就是先寫記憶體,同時記錄redo log,那麼就會返回當前資料已經更新成功。此時,記憶體中的資料頁和磁碟的資料頁是不一致的,稱此為髒頁。正常情況下,資料庫會在適當的時候將buffer pool裡邊的髒頁重新整理到磁碟上。整個過程其實和redo log是沒有任何關係的。只有在崩潰恢復場景中,InnoDB 如果判斷到一個數據頁可能在崩潰恢復的時候丟失了更新,就會將它讀到記憶體,然後讓 redo log 更新記憶體內容。更新完成後,記憶體頁變成髒頁,之後會被重新整理到磁碟。

二、binlog(歸檔日誌模組)

MySQL的架構包括Server層和引擎層。前者是MySQL功能層面的事情,引擎層負責儲存相關的具體事宜。redo log是InnoDB引擎特有的日誌模組;binlog是Server層自帶的日誌模組。

1、為什麼我們還需要binlog日誌?

最開始 MySQL 裡並沒有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒有 crash-safe 的能力,binlog 日誌只能用於歸檔。而 InnoDB 是另一個公司以外掛形式引入 MySQL 的,既然只依靠 binlog 是沒有 crash-safe 能力的,所以 InnoDB 使用另外一套日誌系統——也就是 redo log 來實現 crash-safe 能力。

2、redo log和binlog日誌的不同點如下:

  • redo log是InnoDB引擎特有的日誌模組;binlog是Server層自帶的日誌模組

  • redo log是物理日誌,記錄了某個資料頁上所做的修改;binlog是邏輯日誌,記錄本次修改的原始邏輯,說白了就是記錄了修改資料的SQL語句

  • redo log是迴圈寫的形式,空間固定會被用完;binlog是追加寫的形式,可以寫多個檔案,不會覆蓋之前的日誌。

我們可以通過mysqlbinlog可以解析檢視binlog日誌,在MySQL中binlog的日誌格式有statement,row以及mixed三種方式,為了可以更加準確的記錄歸檔日誌,我們一般選擇row格式做為binlog的日誌格式。特性對比如下:

3、binlog恢復資料庫:

binlog是一種歸檔日誌,只要binlog存在,我們就可以進行資料庫的恢復。假如說,在今天中午12點的時候,發現上午10點執行了錯誤的SQL語句,想把資料庫狀態恢復到上午10點,錯誤語句執行之前。那麼該怎麼辦呢?資料恢復步驟如下:

  • 首先你要拿到最近一次的備份庫
  • 拿到最近備份庫開始到出錯時候的所有binlog(binlog日誌保留時間可以自定義)
  • 使用binlog重放到錯誤發生之前。

三、一條update語句在MySQL內部是如何執行的:

在簡單介紹了redo log和binlog之後,我們回到文章開頭,分析一條update語句在MySQL內部是如何執行的:

  • 執行器要先找儲存引擎找到這一行資料,從記憶體中或者磁碟中,返回給執行器
  • 執行器拿到資料之後進行更新操作,再呼叫引擎介面寫入這行新資料
  • 引擎將這行資料更新到記憶體中,並且將操作記錄寫入redo log中,此時redo log處於prepare狀態。然後告知執行器執行完成隨時可以提交事務了
  • 執行器生成這個操作的binlog,並且將binlog寫入磁碟
  • 執行器呼叫引擎的提交事務介面,引擎將剛剛寫入的redo log改成提交(commit)狀態,更新完成

update語句的執行邏輯圖如下所示:

1、兩階段提交協議:

由執行邏輯圖可以看出,這裡其實是使用了兩階段提交,如果不使用兩階段提交會有如下的問題:

  • 先寫redo log,後寫binlog。會導致異常重啟後redo log多了,binlog缺失。使用binlog恢復的備份庫會缺少一條事務
  • 先寫binlog,後寫redo log。會導致多一條事務出來
  • 總結:不使用兩階段提交會導致資料庫狀態和用日誌恢復出來的資料庫狀態不一致

簡單說,redo log 和 binlog 都可以用於表示事務的提交狀態,而兩階段提交就是讓這兩個狀態保持邏輯上的一致。

既然兩階段提交可以保證資料的一致性,那麼根據上圖update執行示意圖,我們來分析下異常發生的時刻對於資料一致性的影響:

  • 若在寫入binlog日誌之前發生異常crash:由於此時 binlog 還沒寫,redo log 也還沒提交,所以崩潰恢復的時候,這個事務會回滾。這時候,binlog 還沒寫,所以也不會傳到備庫。
  • 若binlog日誌寫完之後發生了異常crash:
    • 崩潰恢復時的判斷規則:
      • 如果 redo log 裡面的事務是完整的,也就是已經有了 commit 標識,則直接提交。
      • 如果 redo log 裡面的事務只有完整的 prepare,則判斷對應的事務 binlog 是否存在並完整:
        • a. 如果是,則提交事務
        • b. 否則,回滾事務

那麼如何判斷binlog日誌是否完整?

其實每一種格式的binlog日誌都是由固定格式的,並且在MySQL 5.6.2 版本以後,還引入了 binlog-checksum 引數,用來驗證 binlog 內容的正確性。對於 binlog 日誌由於磁碟原因,可能會在日誌中間出錯的情況,MySQL 可以通過校驗 checksum 的結果來發現。

redo log 和 binlog 是怎麼關聯起來的?

它們有一個共同的資料欄位,叫XID。崩潰恢復的時候,會按順序掃描 redo log:

  • 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
  • 如果碰到只有 parepare、而沒有 commit 的 redo log,就拿著 XID 去 binlog 找對應的事務。

為什麼處於 prepare 階段的 redo log 加上完整 binlog,重啟就能恢復:

這和資料與備份的一致性有關。在 binlog 寫完以後 MySQL 發生崩潰,這時候 binlog 已經寫入了,之後就會被從庫(或者用這個 binlog 恢復出來的庫)使用。所以,在主庫上也要提交這個事務。採用這個策略,主庫和備庫的資料就保證了一致性。

如果 redo log 寫完,再寫 binlog。崩潰恢復的時候,必須得兩個日誌都完整才可以,這種設計可以嗎?

事務的永續性問題,對於 InnoDB 引擎來說,如果 redo log 提交完成了,事務就不能回滾(如果這還允許回滾,就可能覆蓋掉別的事務的更新)。而如果 redo log 直接提交,然後 binlog 寫入的時候失敗,InnoDB 又回滾不了,資料和 binlog 日誌又不一致了。

日誌模組總結:

  • redo log 用於保證 crash-safe 能力。innodb_flush_log_at_trx_commit 這個引數設定成 1 的時候,表示每次事務的 redo log都直接持久化到磁碟。這個引數設定成 1,可以保證MySQL 異常重啟之後資料不丟失。
  • binlog是一種歸檔日誌。sync_binlog 這個引數設定成 1 的時候,表示每次事務的 binlog 都持久化到磁碟,可以保證 MySQL 異常重啟之後 binlog 不丟失。