什么是兩階段提交
當有數據修改時,會先將修改redo log cache和binlog cache然后在刷入到磁盤形成redo log file,當redo log file全都刷入到磁盤時(prepare 狀態)和提交成功后才能將binlog cache刷入磁盤,當binlog全部刷新到磁盤后會記錄一個xid,然后在relo log file上打上commit標志(commit階段)。
為什么要有兩階段提交
MySQL在修改數據時,MySQL是先從磁盤中將數據copy到內存,然后再將內存中的數據進行修改,並記錄redo log buffer 然后在通過系統調用將事務日志寫入磁盤redo log file 最后最后事務提交后將內存中修改后的數據在開始寫入磁盤中。
1.當只有redo log,binlog失效時,會導致主庫可以通過redo log來重做,而從庫因為沒有及時獲取到binlog而不能進行回放,導致主從數據不一致。這樣會出現 redo log 寫入到磁盤了,但是 binlog 還沒寫入磁盤,於是當發生 crash recovery 時,恢復后,主庫會應用redo log,恢復數據,但是由於沒有 binlog,從庫就不會同步這些數據,主庫比從庫“新”,造成主從不一致
2.跟上一種情況類似,很容易知道,這樣會反過來,造成從庫比主庫“新”,也會造成主從不一致。
而兩階段提交,就解決這個問題,crash recovery 時:
如果 redo log 已經 commit,那毫不猶豫的,把事務提交
如果 redo log 處於 prepare,則去判斷事務對應的 binlog 是不是完整的
是,則把事務提交
否,則事務回滾
兩階段提交,其實是為了保證 redo log 和 binlog 的邏輯一致性。
mysql的兩階段提交原理
(1) perpare階段 寫入redo日志
1、設置undo state=TRX_UNDO_PREPARED;
2、刷事務更新產生的redo日志;
(2) commit階段 寫入binlog日志
1、將事務產生的binlog寫入文件,刷入磁盤;
2、設置undo頁的狀態,置為TRX_UNDO_TO_FREE或TRX_UNDO_TO_PURGE; //標記可以清理回滾段
3、記錄事務對應的binlog偏移,寫入系統表空間。
兩階段提交是跨系統維持數據邏輯一致性時常用的一個方案。兩階段存在阻塞難題,提出的三階段提交,在二階段的基礎上增加了一個預提交。
(3)假設法驗證為什么分兩階段:
A. 先寫 redo log 后寫 binlog。假設在 redo log 寫完,binlog 還沒有寫完的時候,MySQL 進程異常重啟。由於我們前面說過的,redo log 寫完之后,系統即使崩潰,仍然能夠把數據恢復回來,所以恢復后這一行 c 的值是 1。
但是由於 binlog 沒寫完就 crash 了,這時候 binlog 里面就沒有記錄這個語句。因此,之后備份日志的時候,存起來的 binlog 里面就沒有這條語句。
然后你會發現,如果需要用這個 binlog 來恢復臨時庫的話,由於這個語句的 binlog 丟失,這個臨時庫就會少了這一次更新,恢復出來的這一行 c 的值就是 0,與原庫的值不同。
B. 先寫 binlog 后寫 redo log。如果在 binlog 寫完之后 crash,由於 redo log 還沒寫,崩潰恢復以后這個事務無效,所以這一行 c 的值是 0。但是 binlog 里面已經記錄了“把c 從 0 改成 1”這個日志。所以,在之后用 binlog 來恢復的時候就多了一個事務出來,恢復出來的這一行 c 的值就是 1,與原庫的值不同。
(4)redo和binlog這兩種日志有以下三點不同。
1、 redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎都可以使用。
2、 redo log 是物理日志,記錄的是“在某個數據頁上做了什么修改”;binlog 是邏輯日志,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
3、redo log 是循環寫的,空間固定會用完;binlog 是可以追加寫入的。“追加寫”是指 binlog 文件寫到一定大小后會切換到下一個,並不會覆蓋以前的日志。
(5) binlog組提交
引入隊列機制保證innodb commit順序與binlog落盤順序一致,並將事務分組,組內的binlog刷盤動作交給一個事務進行,實現組提交目的。binlog提交將提交分為了3個階段,FLUSH階段,SYNC階段和COMMIT階段。每個階段都有一個隊列,每個隊列有一個mutex保護,約定進入隊列第一個線程為leader,其他線程為follower,所有事情交由leader去做,leader做完所有動作后,通知follower刷盤結束。binlog組提交基本流程如下:
FLUSH 階段
(1) 持有Lock_log mutex [leader持有,follower等待]
(2) 獲取隊列中的一組binlog(隊列中的所有事務)
(3) 將binlog buffer到I/O cache
(4) 通知dump線程dump binlog
SYNC階段
(1) 釋放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待
(2) 將一組binlog 落盤(sync動作,最耗時,假設sync_binlog為1
COMMIT階段
(1) 釋放Lock_sync mutex,持有Lock_commit mutex[leader持有,follower等待]
(2) 遍歷隊列中的事務,逐一進行innodb commit
(3) 釋放Lock_commit mutex
(4) 喚醒隊列中等待的線程
說明:由於有多個隊列,每個隊列各自有mutex保護,隊列之間是順序的,約定進入隊列的一個線程為leader,因此FLUSH階段的leader可能是SYNC階段的follower,但是follower永遠是follower。
MYSQL目前的組提交方式解決了一致性和性能的問題。通過二階段提交解決一致性,通過redo log和binlog的組提交解決磁盤IO的性能。