【參考文章】:MySQL中Redo與Binlog順序一致性問題?
【參考文章】:極客時間
1. 數據更新時的日志處理流程
1.1 redo log(prepare狀態)
此時SQL已經成功執行了,已經產生了語句的redo和undo內存日志,已經進入了事務commit步驟。然后告訴引擎做Prepare完成第一階段,Prepare階段就是寫Prepare Log(Prepare Log也是Redo Log),將事務狀態設為TRX_PREPARED,寫Prepare XID(事務ID號)到Redo Log。寫XID到Redo Log的時候會一並把Redo Log刷新到磁盤,這個時候Redo Log的日志量大小取決於執行SQL語句時產生的Redo是否被刷盤,這個刷盤是隨機的,后台Master線程每秒鍾都會刷新一次。
1.2 binlog
如果事務涉及的所有存儲引擎的Prepare(即Redo Log寫入磁盤之后)都執行成功,則調用TC_LOG_BINLOG::log_xid方法將SQL語句寫到Binlog(write()將binary log內存日志數據寫入文件系統緩存,fsync()將binary log文件系統緩存日志數據永久寫入磁盤),同時也會把XID寫入到Binlog。此時,事務已經鐵定要提交了。否則,調用ha_rollback_trans方法回滾事務,而SQL語句實際上也不會寫到binlog。
Binlog是事務commit時才刷新到磁盤,如果binlog太大則commit時會慢。
1.3 redo log(commit狀態)
最后,調用引擎的Commit完成事務的提交。並且會對事務的undo log從prepare狀態設置為提交狀態(可清理狀態),刷新Commit Log到Redo Log,釋放鎖,釋放mvcc相關的read view等等;將事務設為TRX_NOT_STARTED狀態。
1.4 兩階段提交
由上面的二階段提交流程可以看出,通過兩階段提交方式保證了無論在任何情況下,事務要么同時存在於存儲引擎和binlog中,要么兩個里面都不存在,可以保證事務的binlog和redo log順序一致性。一旦階段2中持久化Binlog完成,就確保了事務的提交。
此外需要注意的是,每個階段都需要進行一次fsync操作才能保證上下兩層數據的一致性。
階段1的fsync由參數innodb_flush_log_at_trx_commit=1控制,階段2的fsync由參數sync_binlog=1控制,俗稱“雙1”,是保證CrashSafe的根本。
1.5 CrashSafe
CrashSafe指MySQL服務器宕機重啟后,能夠保證:
– 所有已經提交的事務的數據仍然存在。
– 所有沒有提交的事務的數據自動回滾。
2. binlog
二進制日志是server層的,主要用來做主從復制和即時點恢復時使用的。
2.1 日志記錄的三種模式
基於SQL語句的復制(statement-based replication,SBR):記錄執行的SQL語句
基於行的復制(row-based replication,RBR):記錄更新的每一條記錄的變化情況
混合模式復制(mixed-based replication,MBR):根據具體的更新語句選擇上述兩種中的一種方式記錄
2.2 設置 binlog 日志模式
靜態設置,配置文件形式
vi my.cnf
binlog_format="STATEMENT"
動態設置,命令形式
mysql> SET GLOBAL binlog_format = 'STATEMENT';
3. redo log
事務日志(redo log)是InnoDB存儲引擎層的,用來保證事務安全的。
3.1 redo log 文件
redo log 是固定大小的,從頭開始寫,寫到末尾又回到開頭循環寫;
redo log 有兩個指針:
一個為 write pos:表示當前記錄的位置,一邊寫一邊后移;
一個為 checkpoint:表示當前要擦除的位置,一邊擦除一邊后移,擦除之前要將記錄寫到磁盤文件中;
4. 區別
binlog 屬於MySQL的 sever 層,所有引擎都可以使用;redo log 屬於 InnoDB引擎特有。
binlog 是邏輯日志,記錄的是SQL語句的原始邏輯;redo log 是物理日志,記錄的是在某個數據頁上做了什么修改。
binlog 是追加寫,一個文件寫滿之后就寫到下一個文件,不會覆蓋之前的文件;redo log 是循環寫,寫到文件末尾之后又從文件起始位置開始寫,會覆蓋之前的日志。
5. 故障恢復
開啟Binary log的MySQL在crash recovery時:MySQL在prepare階段會生成xid,然后會在commit階段寫入到binlog中。在進行恢復時事務要提交還是回滾,是由Binlog來決定的。
– 事務的Xid_log_event存在,就要提交。
– 事務的Xid_log_event不存在,就要回滾。
恢復的過程非常簡單:
– 掃描最后一個Binlog文件(進行rotate binlog文件時,確保老的binlog文件對應的事務已經提交),提取其中的Xid_log_event
– 重做檢查點以后的redo日志,讀取事務的undo段信息,搜集處於prepare階段的事務鏈表,將事務的xid與binlog中的xid對比,若存在,則提交,否則就回滾
總結一下,基本頂多會出現下面是幾種情況:
- 當事務在prepare階段crash,數據庫recovery的時候該事務未寫入Binary log並且存儲引擎未提交,將該事務rollback。
- 當事務在binlog階段crash,此時日志還沒有成功寫入到磁盤中,啟動時會rollback此事務。
- 當事務在binlog日志已經fsync()到磁盤后crash,但是InnoDB沒有來得及commit,此時MySQL數據庫recovery的時候將會讀出二進制日志的Xid_log_event,然后告訴InnoDB提交這些XID的事務,InnoDB提交完這些事務后會回滾其它的事務,使存儲引擎和二進制日志始終保持一致。
總結起來說就是如果一個事務在prepare階段中落盤成功,並在MySQL Server層中的binlog也寫入成功,那這個事務必定commit成功。