MySQL中Redo與Binlog順序一致性問題?


 MySQL  彭東穩  1年前 (2017-01-12)  6656次瀏覽  已收錄  0個評論

首先,我們知道在MySQL中,二進制日誌是server層的,主要用來做主從複製和即時點恢復時使用的。而事務日誌(redo log)是InnoDB存儲引擎層的,用來保證事務安全的。現在我們來討論一下MySQL主從複製過程中的一些細節問題,有關於主從複製可以看具體的章節。

在瞭解了以上基礎的內容後,我們可以帶着以下的幾個問題去學習複製到底是怎樣工作的。

  • 爲什麼MySQL有binlog,還有redo log?
  • 事務是如何提交的?事務提交先寫binlog還是redo log?如何保證這兩部分的日誌做到順序一致性?
  • 爲了保障主從複製安全,故障恢復是如何做的?
  • 爲什麼需要保證二進制日誌的寫入順序和InnoDB層事務提交順序一致性呢?

爲什麼MySQL有binlog,還有redo log?

這個是因爲MySQL體系結構的原因,MySQL是多存儲引擎的,不管使用那種存儲引擎,都會有binlog,而不一定有redo log,簡單的說,binlog是MySQL Server層的,redo log是InnoDB層的。

事務是如何提交的?事務提交先寫binlog還是redo log?如何保證這兩部分的日誌做到順序一致性?

MySQL Binary log在MySQL 5.1版本後推出主要用於主備複製的搭建,我們回顧下MySQL在開啓/關閉Binary Log功能時是如何工作的。

MySQL沒有開啓Binary log的情況下?

首先看一下什麼是CrashSafe?CrashSafe指MySQL服務器宕機重啓後,能夠保證:

– 所有已經提交的事務的數據仍然存在。

– 所有沒有提交的事務的數據自動回滾。

Innodb通過Redo Log和Undo Log可以保證以上兩點。爲了保證嚴格的CrashSafe,必須要在每個事務提交的時候,將Redo Log寫入硬件存儲。這樣做會犧牲一些性能,但是可靠性最好。爲了平衡兩者,InnoDB提供了一個innodb_flush_log_at_trx_commit系統變量,用戶可以根據應用的需求自行調整。

innodb_flush_log_at_trx_commit = 0|1|2

0 – 每N秒將Redo Log Buffer的記錄寫入Redo Log文件,並且將文件刷入硬件存儲1次。N由innodb_flush_log_at_timeout控制。

1 – 每個事務提交時,將記錄從Redo Log Buffer寫入Redo Log文件,並且將文件刷入硬件存儲。

2 – 每個事務提交時,僅將記錄從Redo Log Buffer寫入Redo Log文件。Redo Log何時刷入硬件存儲由操作系統和innodb_flush_log_at_timeout決定。這個選項可以保證在MySQL宕機,而操作系統正常工作時,數據的完整性。

通過redo日誌將所有已經在存儲引擎內部提交的事務應用redo log恢復,所有已經prepare但是沒有commit的transactions將會應用undo log做rollback。然後客戶端連接時就能看到已經提交的數據存在數據庫內,未提交被回滾地數據需要重新執行。

MySQL開啓Binary log的情況下?

MySQL爲了保證master和slave的數據一致性,就必須保證binlog和InnoDB redo日誌的一致性(因爲備庫通過二進制日誌重放主庫提交的事務,而主庫binlog寫入在commit之前,如果寫完binlog主庫crash,再次啓動時會回滾事務。但此時從庫已經執行,則會造成主備數據不一致)。所以在開啓Binlog後,如何保證binlog和InnoDB redo日誌的一致性呢?爲此,MySQL引入二階段提交(two phase commit or 2pc),MySQL內部會自動將普通事務當做一個XA事務(內部分佈式事物)來處理:

– 自動爲每個事務分配一個唯一的ID(XID)。

– COMMIT會被自動的分成Prepare和Commit兩個階段。

– Binlog會被當做事務協調者(Transaction Coordinator),Binlog Event會被當做協調者日誌。

想了解2PC,可以參考文檔:https://en.wikipedia.org/wiki/Two-phase_commit_protocol

Binlog在2PC中充當了事務的協調者(Transaction Coordinator)。由Binlog來通知InnoDB引擎來執行prepare,commit或者rollback的步驟。事務提交的整個過程如下:

MySQL中Redo與Binlog順序一致性問題?

以上的圖片中可以看到,事務的提交主要分爲兩個主要步驟:

1. 準備階段(Storage Engine(InnoDB) Transaction Prepare Phase)

此時SQL已經成功執行,並生成xid信息及redo和undo的內存日誌。然後調用prepare方法完成第一階段,papare方法實際上什麼也沒做,將事務狀態設爲TRX_PREPARED,並將redo log刷磁盤。

2. 提交階段(Storage Engine(InnoDB)Commit Phase)

2.1 記錄協調者日誌,即Binlog日誌。

如果事務涉及的所有存儲引擎的prepare都執行成功,則調用TC_LOG_BINLOG::log_xid方法將SQL語句寫到binlog(write()將binary log內存日誌數據寫入文件系統緩存,fsync()將binary log文件系統緩存日誌數據永久寫入磁盤)。此時,事務已經鐵定要提交了。否則,調用ha_rollback_trans方法回滾事務,而SQL語句實際上也不會寫到binlog。

2.2 告訴引擎做commit。

最後,調用引擎的commit完成事務的提交。會清除undo信息,刷redo日誌,將事務設爲TRX_NOT_STARTED狀態。

PS:記錄Binlog是在InnoDB引擎Prepare(即Redo Log寫入磁盤)之後,這點至關重要。

由上面的二階段提交流程可以看出,一旦步驟2中的操作完成,就確保了事務的提交,即使在執行步驟3時數據庫發送了宕機。此外需要注意的是,每個步驟都需要進行一次fsync操作才能保證上下兩層數據的一致性。步驟2的fsync參數由sync_binlog=1控制,步驟3的fsync由參數innodb_flush_log_at_trx_commit=1控制,俗稱“雙1”,是保證CrashSafe的根本。

參數說明如下:

innodb_flush_log_at_trx_commit(redo)

  • 0: log buffer每秒一次地寫入log file中,且進行flush操作。InnoDB日誌刷新頻率由控制 innodb_flush_log_at_timeout,它允許你將日誌刷新頻率設置爲N秒(其中N是1 … 2700,默認值爲1)。
  • 1:每次事務提交時都會把log buffer的數據寫入log file,並進行flush操作。
  • 2:每次事務提交時MySQL都會把log buffer的數據寫入log file,不進行flush操作。

sync_binlog (binlog)

  • 0:刷新binlog_cache中的信息到磁盤由os決定。
  • N:每N次事務提交刷新binlog_cache中的信息到磁盤。

事務的兩階段提交協議保證了無論在任何情況下,事務要麼同時存在於存儲引擎和binlog中,要麼兩個裏面都不存在,這就保證了主庫與從庫之間數據的一致性。如果數據庫系統發生崩潰,當數據庫系統重新啓動時會進行崩潰恢復操作,存儲引擎中處於prepare狀態的事務會去查詢該事務是否也同時存在於binlog中,如果存在就在存儲引擎內部提交該事務(因爲此時從庫可能已經獲取了對應的binlog內容),如果binlog中沒有該事務,就回滾該事務。例如:當崩潰發生在第一步和第二步之間時,明顯處於prepare狀態的事務還沒來得及寫入到binlog中,所以該事務會在存儲引擎內部進行回滾,這樣該事務在存儲引擎和binlog中都不會存在;當崩潰發生在第二步和第三步之間時,處於prepare狀態的事務存在於binlog中,那麼該事務會在存儲引擎內部進行提交,這樣該事務就同時存在於存儲引擎和binlog中。

爲了保證數據的安全性,以上列出的3個步驟都需要調用fsync將數據持久化到磁盤。由於在引擎內部prepare好的事務可以通過binlog恢復,所以通常情況下第三個fsync是可以省略的。

另外,MySQL內部兩階段提交需要開啓innodb_support_xa=true,默認開啓。這個參數就是支持分佈式事務兩段式事務提交。redo和binlog數據一致性就是靠這個兩段式提交來完成的,如果關閉會造成事務數據的丟失。

爲了保障主從複製安全,故障恢復是如何做的?

開啓Binary log的MySQL在crash recovery時:MySQL在prepare階段會生成xid,然後會在commit階段寫入到binlog中。在進行恢復時事務要提交還是回滾,是由Binlog來決定的。

– 事務的Xid_log_event存在,就要提交。

– 事務的Xid_log_event不存在,就要回滾。

恢復的過程非常簡單:

– 從Binlog中讀出所有的Xid_log_event

– 告訴InnoDB提交這些XID的事務

– InnoDB回滾其它的事務

總結一下,基本頂多會出現下面是幾種情況:

  • 當事務在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成功。

爲什麼需要保證二進制日誌的寫入順序和InnoDB層事務提交順序一致性呢?

上面提到單個事務的二階段提交過程,能夠保證存儲引擎和binary log日誌保持一致,但是在併發的情況下怎麼保證InnoDB層事務日誌和MySQL數據庫二進制日誌的提交的順序一致?當多個事務併發提交的情況,如果Binary Log和存儲引擎順序不一致會造成什麼影響?

這是因爲備份及恢復需要,例如通過xtrabackup或ibbackup這種物理備份工具進行備份時,並使用備份來建立複製,如下圖:

MySQL中Redo與Binlog順序一致性問題?

如上圖,事務按照T1T2T3順序開始執行,將二進制日誌(按照T1、T2、T3順序)寫入日誌文件系統緩衝,調用fsync()進行一次group commit將日誌文件永久寫入磁盤,但是存儲引擎提交的順序爲T2、T3、T1當T2、T3提交事務之後,若通過在線物理備份進行數據庫恢復來建立複製時,因爲在InnoDB存儲引擎層會檢測事務T3在上下兩層都完成了事務提交,不需要在進行恢復了,此時主備數據不一致(搭建Slave時,change master to的日誌偏移量記錄T3在事務位置之後)。

爲了解決以上問題,在早期的MySQL 5.6版本之前,通過prepare_commit_mutex鎖以串行的方式來保證MySQL數據庫上層二進制日誌和Innodb存儲引擎層的事務提交順序一致,然後會導致組提交(group commit)特性無法生效。爲了滿足數據的持久化需求,一個完整事務的提交最多會導致3次fsync操作。爲了提高MySQL在開啓binlog的情況下單位時間內的事務提交數,就必須減少每個事務提交過程中導致的fsync的調用次數。所以,MySQL從5.6版本開始加入了binlog group commit技術(MariaDB 5.3版本開始引入)。

MySQL數據庫內部在prepare redo階段獲取prepare_commit_mutex鎖,一次只能有一個事務可獲取該mutex。通過這個臭名昭著prepare_commit_mutex鎖,將redo log和binlog刷盤串行化,串行化的目的也僅僅是爲了保證redo log和Binlog一致,繼而無法實現group commit,犧牲了性能。整個過程如下圖:

MySQL中Redo與Binlog順序一致性問題?

上圖可以看出在prepare_commit_mutex,只有當上一個事務commit後釋放鎖,下一個事務纔可以進行prepare操作,並且在每個事務過程中Binary log沒有fsync()的調用。由於內存數據寫入磁盤的開銷很大,如果頻繁fsync()把日誌數據永久寫入磁盤數據庫的性能將會急劇下降。此時MySQL數據庫提供sync_binlog參數來設置多少個binlog日誌產生的時候調用一次fsync()把二進制日誌刷入磁盤來提高整體性能。

上圖所示MySQL開啓Binary log時使用prepare_commit_mutex和sync_log保證二進制日誌和存儲引擎順序保持一致,prepare_commit_mutex的鎖機製造成高併發提交事務的時候性能非常差而且二進制日誌也無法group commit。

這個問題早在2010年的MySQL數據庫大會中提出,Facebook MySQL技術組,Percona公司都提出過解決方案,最後由MariaDB數據庫的開發人員Kristian Nielsen完成了最終的”完美”解決方案。在這種情況下,不但MySQL數據庫上層二進制日誌寫入是group commit的,InnoDB存儲引擎層也是group commit的。此外還移除了原先的鎖prepare_commit_mutex,從而大大提高了數據庫的整體性。MySQL 5.6採用了類似的實現方式,並將其稱爲BLGC(Binary Log Group Commit),並把事務提交過程分成三個階段,Flush stage、Sync stage、Commit stage。具體看:MySQL Group Commit

<參考>

淺談mysql的兩階段提交協議

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章