mysql在事務執行時,需要寫入兩種日誌,一種是server層的binlog,另一種是引擎層的redo log。事務commit時,以上兩種類型的日誌的寫入需要遵循兩段式提交協議。
什麼是兩段式提交?
爲了保證分佈式事務場景下事務的一致性。因爲在分佈式背景下,事務語句來自不同實例,因此需要一個協調者角色。每一個實例的語句先執行prepare,並且將結果告知協調者,如果全部成功,協調者再發出commit命令,各個實例再執行提交。防止部分實例失敗,導致分佈式事務語句沒有全部執行。當然兩階段提交併不能完全解決問題,只是一種思路。
mysql裏,因爲binlog和redo log也是不同層面的兩種日誌,也有分佈式事務的特徵,即要麼全部成功要麼全部失敗,所以也使用了兩段式提交的思路。
爲什麼必須兩段式提交?
因爲如果redo log和binlog只有一個執行成功,將會導致主從不一致。比如只有redo log成功,那麼從庫無法更新;如果只有binlog成功,主庫無法更新。
redo log和binlog爲什麼必須是兩個?能否合二爲一?
個人認爲,理論上設計一種新的log,可以具備redo log和binlog的全部特性,那當然是可以的。但是,mysql在發展過程中的演化,導致了現在兩種log並存的場面。redo log是innodb引擎特有的日誌,在引擎內生成,用於保證事務的持久性。binlog是mysql的server層日誌,用於做歸檔,可以複製和增量備份。binlog是server層特性,與用什麼引擎沒有關係。
那麼,在目前的情況下,二者是否可以合二爲一?
比如只用redo log,不行,因爲只有innodb採用redo log,這樣換成其他引擎不就玩完了?什麼,讓其他引擎也強加上redo log?那也不行,因爲redo log不支持歸檔,redo log是循環寫入的,寫完後就必須清空之前的文件才能繼續寫入;
那隻用binlog,也不行,因爲binlog並不會記錄更新的細節信息,比如修改了哪個頁的哪一個偏移位置的數據,只有binlog缺乏恢復數據的能力。
redo log和binlog如何關聯?
有一個公共的字段叫做XID,在兩個日誌裏都存在。可以當成是事務的唯一id。
兩段式提交過程?
先執行redo log的prepare階段。會將事務內容寫入redo log。事務處於prepare階段。具體怎麼寫由innodb_flush_log_at_trx_commit參數控制:
0:只寫入log buffer,不持久化;
1:調用fsync持久化至磁盤;
2:調用write寫入文件系統的page cache,由操作系統決定持久化時機;
另外,mysql會有一個後臺線程每隔一秒將log buffer的redo log持久化至磁盤,fsync方式。
一般會選擇設置爲2,這樣性能好一些,但是如果在fsync之前,系統掛了,redo log就丟了。而對於金融類的系統,需要較高可靠性,可以設置爲1。
接着會執行binlog的寫入,在事務提交之前,binlog也是記錄到binlog cache中的,在提交時,會將binlog持久化,具體行爲由另一個參數sync_binlog控制:
0:調用write寫入文件系統的page cache;
1:調用fsync持久化至磁盤;
N:每次提交只write,直到累積N個事務後再fsync;
同樣的,對於金融類等系統,需要設置爲1。兩個參數都設置爲1的配置方式被稱爲“雙1配置”,可以保證不丟數據。
最後執行兩段式提交的commit,將本地redo log commit;
如何恢復數據?
如果寫binlog前掛了,事務不會在主庫和從庫提交,需要回滾這部分事務;
如果commit前掛了,事務處於prepare,但是已經寫了binlog,由於binlog有可能沒有fsync,所以需要判斷binlog是否完整。怎麼取?從本地redo log中取到對應事務的XID,看XID對應的事務是否已經在binlog中被提交,如果是,則提交本地redo log,否則回滾;那又是如何判斷binlog是否完整?如果是語句模式,會有commit語句;如果是行模式,會有XID Event;
當然由於prepare階段redo log的並一定fsync,所以這裏可能會丟本地數據。
組提交
實際上,爲了優化fsync性能,binlog和redo log在持久化時都使用了組提交機制,也就是一組日誌一起fsync,提升了性能。
如何保證兩個log的執行順序
待補充