最近一直在看一些關於mysql innodb事務提交,innodb crash recovery,內部XA事務等資料,整理一下我對這塊的一個初步理解。但願看完後能對你們理解mysql innodb事務提交過程,以及如何作crash recovery有個大概的瞭解html
爲了性能考慮,每次提交事務的時候,只須要將redo和undo落盤就表明事務已經持久化了,而不須要等待數據落盤。這樣就已經能保證事務的crash時的前滾或者回滾。因爲undo的信息也會寫入redo,因此其實咱們只須要根據redo是否落盤而決定crash recovrey的時候是重作仍是回滾。而上面提到,開啓binlog後,還須要考慮binlog是否落盤(binlog牽扯到主從數據一致性,全備恢復的位點)。根據事務是否成功寫binlog決定事務的重作仍是回滾。mysql
2PC即innodb對於事務的兩階段提交機制。當mysql開啓binlog的時候,會存在一個內部XA的問題:事務在存儲引擎層(redo)commit的順序和在binlog中提交的順序不一致的問題。sql
將事務的commit分爲prepare和commit兩個階段:
一、prepare階段:redo持久化到磁盤(redo group commit),並將回滾段置爲prepared狀態,此時binlog不作操做。數據庫
二、commit階段:innodb釋放鎖,釋放回滾段,設置提交狀態,binlog持久化到磁盤,而後存儲引擎層提交併發
對2PC有了一個瞭解以後,咱們能夠發現上面圖中redo和binlog均可以group commit,那麼下面瞭解下什麼是group commit運維
咱們知道,日誌的寫入基本上是順序IO。WAL(Write-Ahead-Logging)用順序的日誌寫入代替數據的隨機IO實現事務持久化。可是儘管如此,每次事務提交都須要日誌刷盤,仍然受限於磁盤IO。group commit的出現就是爲了將日誌(redo/binlog)刷盤的動做合併,從而提高IO性能分佈式
2PC機制只能解決單個事務的redo/binlog順序一致的問題,若是併發怎麼辦呢(有可能存在一種狀況:binlog提交順序(T1,T2,T3),innodb commit順序(T2,T3,T1))。高併發
如上圖,事務按照T一、T二、T3順序開始執行,將二進制日誌(按照T一、T二、T3順序)寫入日誌文件系統緩衝,調用fsync()進行一次group commit將日誌文件永久寫入磁盤,可是存儲引擎提交的順序爲T二、T三、T1。當T二、T3提交事務以後,若經過在線物理備份進行數據庫恢復來創建複製時,由於在InnoDB存儲引擎層會檢測事務T3在上下兩層都完成了事務提交,不須要在進行恢復了,此時主備數據不一致性能
MySQL 5.6版本以前,經過prepare_commit_mutex鎖以串行的方式來保證MySQL數據庫上層二進制日誌和Innodb存儲引擎層的事務提交順序一致,而後會致使group commit沒法生效優化
上圖所示MySQL開啓Binary log時使用prepare_commit_mutex和sync_log保證二進制日誌和存儲引擎順序保持一致,prepare_commit_mutex的鎖機制形成高併發提交事務的時候性能很是差並且二進制日誌也沒法group commit。
2PC中的prepare階段,會對redo進行一次刷盤操做(innodb_flush_log_at_trx_commit=1),這時候redo group commit的過程以下:
這個過程是根據LSN的順序進行合併的,也就是說一次redo group commit的過程可能會講別的未提交事務中的lsn也一併刷盤
每一個事務提交時,都會觸發一次redo flush動做,因爲磁盤讀寫比較慢,所以很影響系統的吞吐量。淘寶作了一個優化,將prepare階段的刷redo動做移到了commit(flush-sync-commit)的flush階段以前,保證刷binlog以前,必定會刷redo。這樣就不會違背原有的故障恢復邏輯。移到commit階段的好處是,能夠不用每一個事務都刷盤,而是leader線程幫助刷一批redo。如何實現,很簡單,由於log_sys->lsn始終保持了當前最大的lsn,只要咱們刷redo刷到當前的log_sys->lsn,就必定能保證,將要刷binlog的事務redo日誌必定已經落盤。經過延遲寫redo方式,實現了redo log組提交的目的,並且減小了log_sys->mutex的競爭。目前這種策略已經被官方mysql5.7.6引入。
Binlog Group Commit的基本思想是引入隊列機制,保證innodb commit的順序與binlog落盤的順序一致,並將事務分組,組內的binlog刷盤動做交給一個事務進行,實現組提交目的。隊列中的第一個事務稱爲leader,其餘事務稱爲follower。全部事情交給leader去作。過程分爲三個階段
Flush Stag
將每一個事務的binlog寫入內存
1) 持有Lock_log mutex [leader持有,follower等待]。
2) 獲取隊列中的一組binlog(隊列中的全部事務)。
3) 將binlog buffer到I/O cache。
4) 通知dump線程dump binlog。
Sync Stage
將內存中的二進制日誌刷新到磁盤,若隊列中有多個事務,那麼僅一次fsync操做就完成了二進制日誌的寫入,這就是BLGC。
1) 釋放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待]。
2) 將一組binlog 落盤(sync動做,最耗時,假設sync_binlog爲1)。
Commit Stage
leader根據順序調用存儲引擎層事務的提交,Innodb自己就支持group commit,所以修復了原先因爲鎖prepare_commit_mutex致使group 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。
當有一組事務在進行commit階段時,其餘新事物能夠進行Flush階段,從而使group commit不斷生效。group commit的效果由隊列中事務的數量決定,若每次隊列中僅有一個事務,那麼可能效果和以前差很少,甚至會更差。但當提交的事務越多時,group commit的效果越明顯,數據庫性能的提高也就越大。
還有一點要說明:sync_binlog=1這個參數單位表示的是一組事務,而並不是一個事務
因爲未提交的事務和已回滾的事務也會記錄到redo log中,所以在進行恢復的時候,這些事務要進行特殊的處理
innodb的處理策略是:進行恢復時,從checkpoint開始,重作全部事務(包括未提交的事務和已回滾的事務),而後經過undo log回滾那些未提交的事務
一、掃描最後一個 binlog,提取xid(標識binlog中的第幾個event)
二、xid也會寫到redo中,將redo中prepare狀態的xid,去跟最後一個binlog中的xid比較 ,若是binlog中存在,則提交,不然回滾
爲何只掃描最後一個binlog?由於binlog rotate的時候會把前面的binlog都刷盤,並且事務是不會跨binlog的
MySQL Group Comit - 運維那點事
MySQL 外部XA及其在分佈式事務中的應用分析
MySQL undo,redo,2PC,恢復思惟導圖
MYSQL-GroupCommit
MySQL的Crash Safe和Binlog的關係 - 老葉茶館