MySQL日誌有3種:
redo
官方文檔:https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html
redo log是重做日誌,提供前滾操作。
redo log是物理日誌,記錄的是數據行的物理修改,當數據需要恢復時,可以從redo log中恢復到數據最後一次的修改狀態。
redo log包含兩塊內容,一部分是日誌緩衝(log buffer),在內存中;另一部分是日誌重做文件(log file),在磁盤上。
InnoDB通過force log at commit實現事務的持久性,在事務提交的時候,必須先將該事務的所有事務日誌寫入redo log file和undo log file中。
流程:
我們來看下日誌的寫入過程:
如上圖所示,我們來梳理下redo log的處理流程:
- 當數據發生了修改,InnoDB引擎先將該記錄寫入redo log buffer中,更新內存中的數據,此時更新就算是完成了。
- 在適當的時機,InnoDB引擎調用os方法,將buffer中的數據寫入磁盤。
另外redo log是固定大小的,是循環寫,當空間用完之後,後續記錄會將最老的記錄替換掉。
flush data
那MySQL是在什麼時候將buffer中的數據寫入log file呢?
通過配置:innodb_flush_log_at_trx_commit來決定,有0,1,2三種值,默認爲1:
- 爲0的時候,事務提交時不會將log buffer中日誌寫入到os buffer,而是每秒寫入os buffer並調用fsync()寫入到log file on disk中。也就是說設置爲0時是(大約)每秒刷新寫入到磁盤中的,當系統崩潰,會丟失1秒鐘的數據。
- 爲1的時候,事務每次提交都會將log buffer中的日誌寫入os buffer並調用fsync()刷到log file on disk中。這種方式即使系統崩潰也不會丟失任何數據,但是因爲每次提交都寫入磁盤,IO的性能較差。
- 爲2的時候,每次提交都僅寫入到os buffer,然後是每秒調用fsync()將os buffer中的日誌寫入到log file on disk。
數據恢復:
log file以log block爲單位進行存儲,一個block大小爲512字節,
write point:數據已經刷新到磁盤上的位置點
check point:數據已經完整刷新到磁盤上的位置點
當啓動InnoDB時,無論上一次是正常關閉還是異常關閉,重啓InnoDB時,都會從check point開始到write point爲止進行恢復數據。
undo
官方文檔:https://dev.mysql.com/doc/refman/8.0/en/innodb-undo-logs.html
undo log是回滾日誌,提供回滾操作。
在InnoDB中,使用undo log來實現多版本併發控制。
在操作任何數據之前,首先先將數據備份到一個地方(這個數據備份的地方就是undo log),任何進行數據修改。如果出現錯誤或者用戶執行rollback,MySQL可以用 undo log的備份數據,將數據恢復到數據開始之前。
與redo log不同,redo log是物理日誌,undo log是邏輯日誌:
- 當delete一條記錄,undo log中會新增一條對應的insert記錄
- 當insert一條記錄,undo log中會新增一條對應的delete記錄
- 當update,undo log中會新增一條相反的update記錄
當事務提交後,InnoDB不會立即刪除undo log中的記錄,會將該事務對應的undo log放入到刪除列表中,未來通過purge來刪除。
binlog
binlog是MySQL server的日誌文件,在server層,主要負責MySQL功能層面的事情。
與redo log的區別:
- redo,undo是InnoDB獨有的,binlog是所有引擎都可以使用的
- redo是物理日誌,記錄的是對某個數據頁做了哪些具體的修改,undo,binlog是邏輯日誌,記錄原始邏輯
- redo是循環寫,空間會用完,binlog是文件追加,不會覆蓋之前的信息
數據恢復:
找一個備份的時間點,把備份的binlog取出來,重放到要恢復的那個時刻
事務
我們來簡單回顧下數據庫事務的幾大特性:
- Atomicity:原子性,通過undo日誌實現
- Consistency:一致性,通過A+I+D三個特性組合保證
- Isolation:隔離性,通過鎖
- Durability:持久性,通過redo日誌實現
簡單梳理下數據庫事務的處理過程:
以update事務爲例,流程大體如下:
- 執行器先從引擎中找到數據,如果數據不在內存,則從磁盤中加載數據進入內存
- InnoDB數據引擎拿到數據之後,將數據在內存中進行更新,同時將數據記錄寫入redo,undo,此時數據處於prepare階段,InnoDB引擎通知執行器操作完成
- 執行器寫入本次操作的binlog
- 執行器調用引擎的事務提交接口,引擎把剛寫入的redo,undo狀態改成commit,同時將binlog寫入磁盤,事務完成
兩階段:
我們可以看到上述流程,先是經歷了prepare階段,再是commit階段,爲什麼要設計經歷兩個階段呢,直接寫入redo,binlog會有什麼問題呢?
現有一條記錄數據列:a=1,執行update指令:a=2,
- 如果一次性先寫入redo,再寫入binlog:
假設redo log已經寫入完成,binlog還沒有寫入完成,此時進程異常,MySQL退出。後面再重啓數據庫,通過redo log是可以將數據恢復到a=2的,因爲redo log裏面是有記錄這個操作的。但是binlog裏面是沒有這個操作的記錄,後續通過備份日誌進行數據恢復,因binlog中這個記錄丟失,恢復後的數據是a=1,而實際應該是a=2,恢復後的數據是不準確的。 - 如果一次性先寫入binlog,再寫入redo:
情形是類似的,binlog寫入完成,而redo沒有完成寫入,那麼InnoDB重啓之後,數據是無法恢復到a=2,最終的數據還是a=1,而用binlog恢復之後得到的數據確實a=2。
通過兩段式提交,確保redo,binlog裏面記錄的數據,最終是一致的。
PS:binlog默認是關閉的,需要手動開啓。在binlog關閉狀態下,事務是不需要兩階段提交的,參與者就只有redo和undo,中間需要通過互斥鎖來防止資源競爭。