MYSQL 一個事務在提交的時候能夠保證binlog和redo log是同時提交的,並且能在當機恢復後保持binlog 和redo log的一致性。
先來看看什麼是redo log 和binlog,以及為什麼要保持它們的一致性。
什麼是redo log,binlog
redo log
是innodb引擎層產生的日誌, MYSQL從磁碟讀取資料的單位是一頁,當修改頁中某條資料時,該行所在的資料頁就變成了髒頁
,由於髒頁並不會立馬重新整理到磁碟,所以redo log會記錄下資料頁進行了哪些變動,用於服務崩潰時的資料恢復
。redo log是固定大小的,由多個檔案組成一個環形的結構,
redo log由兩個指標,write pos 和checkpoint,都是順時針移動,write pos 記錄redo log當前寫入的位置, checkpoint往前移動,就代表就移動過的redo log記錄的髒頁重新整理到磁碟上。所以,write pos
和checkpoint
之間的位置就代表redo log 還可以寫的空間大小,當write pos等於checkpoint時,MYSQL則必須等待髒頁重新整理完畢後才能繼續進行修改操作。
binlog
是mysql server服務層產生的日誌。兩者的用途也不一樣。binlog
則主要用於資料庫的備份,主從同步。binlog記錄的是行變化,記錄格式也有3種,statement,row,mixed,這裡就不細講了。
在瞭解了redo log 和binlog的含義和各自的作用後,我們先來看看它們在一次sql更新中是如何運作的。
sql 更新過程詳解
來看下在一次事務過程中,它們的工作機制。假設我們在進行修改操作,那麼可以用下面的流程圖來表示,
1,首先判斷要修改的資料是否在記憶體裡,沒有的話就從磁碟讀取到記憶體。
2,寫入redo log,注意這裡寫入的redo log僅僅是prepare狀態,只有等到正式提交的時候才會變成commit狀態。並且寫入redo log也不是直接落盤,其實是寫入到了redo log buffer
中,落盤時機受到innodb_flush_log_at_trx_commit
引數控制。
MYSQL會有一個後臺執行緒,定時重新整理redo log buffer 中的資料到磁碟上。除此以外,當innodb_flush_log_at_trx_commit 值為1
時 redo log則會在在prepare階段將redo log buffer 中的資料落入磁碟。
注意📢📢📢,這裡說的事務提交的時候redo log buffer中的資料刷到磁碟上,並不僅僅是執行的當前事務,比如A,B兩個事務,A事務執行到一半,寫了部分資料到redo log buffer,那麼B此時提交事務,同樣也會將A事務的redo log 刷到磁碟上。
當innodb_flush_log_at_trx_commit 值為 0
時,redo log buffer則不會在prepare或者事務提交時刷盤,而是由後臺定時任務定時重新整理redo log buffer中的資料到磁碟上。
當innodb_flush_log_at_trx_commit 值為2時
,則是將redo log buffer中的內容重新整理到檔案系統快取中,由作業系統決定何時重新整理到磁碟上。
所以可以看到,在事務執行過程中,redo log是可能一部分在記憶體,一部分已經落入磁碟了
3, 在寫完redo log後,會去寫binlog,寫binlog同樣不是直接寫檔案,而是寫到binlog cache中,那麼binlog是何時重新整理到磁碟上呢,這個是由sync_bin
引數決定的。
-
sync_binlog = 0
:提交事務時,將記憶體中的binlog cache寫到檔案系統快取中,後續交由作業系統決定何時將資料持久化到磁碟 -
sync_binlog = 1
:提交事務時,將binlog cache中的資料寫入到檔案系統快取,並立馬重新整理到磁碟。 -
sync_binlog =N(N>1)
:提交事務時,都寫到檔案系統快取,但累積 N 個事務後才 fsync 重新整理到磁碟。
4, 最後一步便是對事物進行提交,按引數設定分別對redo log和binlog進行落盤處理。
為什麼要保證binlog 和redo log 同時提交
看完了整個sql更新過程,先說下結論,將innodb_flush_log_at_trx_commit
和 sync_binlog
都設定為1 能夠保證binlog 和redo log 同時提交。
再來看看如果redo log和binlog不同時提交會導致什麼問題❓
redo log和binlog不同時提交會導致
主備不一致
如果在一個事務提交過程中, binlog寫入成功了,此時主庫當機,redo log寫入失敗,主庫恢復後,那麼binlog可能就會被從庫拿去執行,然而主庫的redo log是沒有修改資料的,所以造成主備不一致。
換過來,redo log寫入成功,但是binlog提交失敗,從庫就會缺失新的修改資料,造成主備不一致。
兩階段提交避免資料不一致
接著,我們來細聊 MYSQL在上述sql更新過程中,是如何保證redo log和binlog是同時提交的。
上述事務執行過程中,可以看到對於redo log的提交分了兩個階段,第一個是redo log的prepare
階段,第二個是commit
階段。
當機恢復時,redo log執行恢復的邏輯概括如下,
1,只要redo log變成了commit狀態,MYSQL就認為事務是成功了。
2,而恢復時,發現redo log是prepare 狀態的話,就會去判斷對應事務的binlog 是否完整,完整則對還未提交的事務進行提交,不完整則回滾事務。
我們來分析下異常的情況:
如上圖所示,
1,在第一種異常情況下,redo log 和binlog都沒有寫入,主備是一致的。
2,第二和第三種異常情況, redo log
已經落入磁碟,最後就看binlog是否完整了,完整當機恢復後進行事務提交,備庫即使得到binlog,也能保證與主庫恢復後事務提交的資料 保持一致。
📢📢📢需要注意的是,innodb_flush_log_at_trx_commit 為1
時才能保證redo log是在binlog寫入前是已經落盤的,如果是0或者2,則有可能出現節點崩潰時,redo log沒有寫入到磁碟而丟失,而binlog是完整的情況,造成主備不一致。
兩階段提交帶給業務開發上的思考🤔
從MYSQL 實現兩階段提交的邏輯,可以歸納下,它是如何做到對兩個業務做到最終一致的。
我舉個業務上的例子, 比如有A,B兩個服務,A服務依賴B服務,如何保證在A服務上的資料操作和請求B服務介面這兩個動作同時成功或失敗❓
我直接說下結論,
借鑑兩階段提交的邏輯,我們可以將A服務的資料操作在業務設計上增加一個預扣減的概念,先鎖定A服務資料資源,然後去請求B服務的介面,失敗的話,則釋放A服務鎖定的資料資源,成功的話則進行真實的扣減。
除此以外,還需要增加一個對A服務資料進行補償修復的定時任務
,類似與MYSQL資料庫當機根據binlog是否完整看事務是否提交一樣,定時任務定期檢視還沒有終結的A服務資料,拎出來請求B服務檢視業務成功狀態,B服務返回成功,則將A服務的業務資料進行真實扣減,否則釋放A服務鎖定的資料資源。
透過兩階段提交,來檢視業務的最終一致性。
最後,
自薦一波✅:
歡迎朋友們關注我的公眾號📢📢:【藍胖子的程式設計夢】!
學習容器知識🐳,效能監控🚀,Golang🐋 相關程式設計知識