mysql如何保證數據一致性

1.MySQL數據庫層丟數據場景

   本節咱們主要介紹一下在存儲引擎層上是如何會丟數據的。mysql

1.1.InnoDB丟數據

    InnoDB支持事務,同Oracle相似,事務提交須要寫redo、undo。採用日誌先行的策略,將數據的變動在內存中完成,而且將事務記錄成redo,順序的寫入redo日誌中,即表示該事務已經完成,就能夠返回給客戶已提交的信息。可是實際上被更改的數據還在內存中,並無刷新到磁盤,即尚未落地,當達到必定的條件,會觸發checkpoint,將內存中的數據(page)合併寫入到磁盤,這樣就減小了離散寫、IOPS,提升性能。sql

    在這個過程當中,若是服務器宕機了,內存中的數據丟失,當重啓後,會經過redo日誌進行recovery重作。確保不會丟失數據。所以只要redo可以實時的寫入到磁盤,InnoDB就不會丟數據。數據庫

先來看一下innodb_flush_log_at_trx_commit這個參數:緩存

    = 0 :每秒 write cache & flush disk安全

    = 1 :每次commit都 write cache & flush disk服務器

    = 2 :每次commit都 write cache,而後根據innodb_flush_log_at_timeout(默認爲1s)時間 flush disk網絡

    從這三個配置來看,顯然innodb_flush_log_at_trx_commit=1最爲安全,由於每次commit都保證redo寫入了disk。可是這種方式性能對DML性能來講比較低,在咱們的測試中發現,若是設置爲2,DML性能要比設置爲1高10倍左右。架構

    爲何oracle的實時寫要比innodb的實時寫性能更好?線程與進程?後面還須要研究oracle

你們能夠考慮一下0與2的區別?異步

   在某些DML操做頻繁的場景下,庫的innodb_flush_log_at_trx_commit須要設置爲2,這樣就存在丟數據的風險:當服務器出現宕機,重啓後進行crash recovery則會丟失innodb_flush_log_at_timeout秒內的數據。

   

PS:當開啓了內部XA事務(默認開啓),且開啓binlog,狀況稍有不同,後面會進行介紹。

1.2.MyISAM丟數據

    MyISAM存儲引擎在咱們的生產中用的並很少,可是系統的數據字典表元數據等都是存儲在MyISAM引擎下。

    MyISAM不支持事務,且沒有data cache,全部DML操做只寫到OS cache中,flush disk操做均由OS來完成,所以若是服務器宕機,則這部分數據確定會丟失。

2.主從複製不一致

   主從複製原理:MySQL主庫在事務提交時寫binlog,並經過sync_binlog參數來控制binlog刷新到磁盤「落地」,而備庫經過IO線程從主庫讀取binlog,並記錄到本地的relay log中,由本地的SQL線程再將relay log的數據應用到本地數據庫,以下圖所示:

        25704976_14149949148R0f.jpg

   從上圖咱們能夠看到,在主從環境中,增長了binlog,這就增長了環境的複雜性,所以也增長了丟數據以及數據不一致可能。

在分析這些丟數據的可能性以前,咱們先了解一下binlog的刷新機制以及MySQL的內部XA事務是如何保證binlog與redo的一致性的。

2.1.binlog刷新機制

    master寫binlog與innodb引擎寫redo相似,也有參數控制:sync_binlog

          = 0 :表示MySQL不控制binlog的刷新,由文件系統本身控制它的緩存的刷新

          > 0 :表示每sync_binlog次事務提交,MySQL調用文件系統的刷新操做將緩存刷下去

    其中最安全的就是=1,表示每次事務提交,MySQL都會把binlog緩存刷下去,這樣在掉電等狀況下,系統纔有可能丟失1個事務的數據。當sync_binlog設置爲1,對系統的IO消耗也是很是大的。

2.2.內部XA事務原理

   MySQL的存儲引擎與MySQL服務層之間,或者存儲引擎與存儲引擎之間的分佈式事務,稱之爲內部XA事務。最爲常見的內部XA事務存在與binlog與InnoDB存儲引擎之間。在事務提交時,先寫二進制日誌,再寫InnoDB存儲引發的redo日誌。對於這個操做要求必須是原子的,即須要保證二者同時寫入,內部XA事務機制就是保證二者的同時寫入。

   

   XA事務的大體流程:

  1. 事務提交後,InnoDB存儲引擎會先作一個PREPARE操做,將事務的XID寫入到redo日誌中。

  2. 寫binlog日誌

  3. 再將該事務的commit信息寫到redo log中

            25704976_14149949127kIU.png

     若是在步驟1和步驟2失敗的狀況下,整個事務會回滾,若是在步驟3失敗的狀況下,MySQL數據庫在重啓後會先檢查準備的UXID事務是否已經提交,若沒有,則在存儲引擎層再進行一次提交操做。這樣就保證了redo與binlog的一致性,防止丟數據。

2.3.master庫寫redo、binlog不實時丟數據的場景

     上面咱們介紹了MySQL的內部XA事務流程,可是這個流程並非完美無缺的,redo的ib_logfile與binlog日誌若是被設置非實時flush,就有可能存在丟數據的狀況。

         1.redo的trx_prepare未寫入,但binlog寫入,形成從庫數據量比主庫多。

         2.redo的trx_prepare與commit都寫入了,可是binlog未寫入,形成從庫數據量比主庫少。


從目前來看,只能犧牲性能去換取數據的安全性,必需要設置redo和binlog爲實時刷盤,若是對性能要求很高,則考慮使用SSD

2.4.slave庫寫redo、binlog不實時丟數據的場景

    master正常,可是slave出現異常的狀況下宕機,這個時候會出現什麼樣的狀況呢?若是數據丟失,slave的SQL線程還會從新應用嗎?這個咱們須要先了解SQL線程的機制。

    slave讀取master的binlog日誌後,須要落地3個文件:relay log、relay log info、master info:

        relay log:即讀取過來的master的binlog,內容與格式與master的binlog一致

        relay log info:記錄SQL Thread應用的relay log的位置、文件號等信息

        master info:記錄IO Thread讀取master的binlog的位置、文件號、延遲等信息

    所以若是當這3個文件若是不及時落地,則主機crash後會致使數據的不一致。

在MySQL 5.6.2以前,slave記錄的master信息以及slave應用binlog的信息存放在文件中,即master.info與relay-log.info。在5.6.2版本以後,容許記錄到table中,參數設置以下:

		
			
			
				master-info-repository  = TABLE 
			

			
				relay-log-info-repository = TABLE 
			

		

對應的表分別爲mysql.slave_master_info與mysql.slave_relay_log_info,且這兩個表均爲innodb引擎表。

master info與relay info還有3個參數控制刷新:

  • sync_relay_log:默認爲10000,即每10000次sync_relay_log事件會刷新到磁盤。爲0則表示不刷新,交由OS的cache控制。

  • sync_master_info:若master-info-repository爲FILE,當設置爲0,則每次sync_master_info事件都會刷新到磁盤,默認爲10000次刷新到磁盤;若master-info-repository爲TABLE,當設置爲0,則表不作任何更新,設置爲1,則每次事件會更新表 #默認爲10000

  • sync_relay_log_info:若relay_log_info_repository爲FILE,當設置爲0,交由OS刷新磁盤,默認爲10000次刷新到磁盤;若relay_log_info_repository爲TABLE,且爲INNODB存儲,則不管爲任何值,則都每次evnet都會更新表。

建議參數設置以下:

		
			
			
				sync_relay_log = 1 
			

			
				sync_master_info = 1 
			

			
				sync_relay_log_info = 1 
			

			
				master-info-repository  = TABLE 
			

			
				relay-log-info-repository = TABLE 
			

		

當這樣設置,致使調用fsync()/fdatasync()隨着master的事務的增長而增長,且若slave的binlog和redo也實時刷新的話,會帶來很嚴重的IO性能瓶頸。

2.5.master宕機後沒法及時恢復形成的數據丟失

   當master出現故障後,binlog未及時傳到slave,或者各個slave收到的binlog不一致。且master沒法在第一時間恢復,這個時候怎麼辦?

   若是master不切換,則整個數據庫只能只讀,影響應用的運行。

   若是將別的slave提高爲新的master,那麼原master將來得及傳到slave的binlog的數據則會丟失,而且還涉及到下面2個問題。

      1.各個slave之間接收到的binlog不一致,若是強制拉起一個slave,則slave之間數據會不一致。

      2.原master恢復正常後,因爲新的master日誌丟棄了部分原master的binlog日誌,這些多出來的binlog日誌怎麼處理,從新搭建環境?

對於上面出現的問題,一種方法是確保binlog傳到從庫,或者說保證主庫的binlog有多個拷貝。第二種方法就是容許數據丟失,制定必定的策略,保證最小化丟失數據。

1.確保binlog所有傳到從庫

   方案一:使用semi sync(半同步)方式,事務提交後,必需要傳到slave,事務才能算結束。對性能影響很大,依賴網絡適合小tps系統。

   方案二:雙寫binlog,經過DBDR OS層的文件系統複製到備機,或者使用共享盤保存binlog日誌。

   方案三:在數據層作文章,好比保證數據庫寫成功後,再異步隊列的方式寫一份,部分業務能夠藉助設計和數據流解決。

2.保證數據最小化丟失

   上面的方案設計及架構比較複雜,若是能容忍數據的丟失,能夠考慮使用淘寶的TMHA複製管理工具。

       當master宕機後,TMHA會選擇一個binlog接收最大的slave做爲master。當原master宕機恢復後,經過binlog的逆向應用,把原master上多執行的事務回退掉。

3.總結

     經過上面的總結分析,MySQL丟數據的場景是五花八門,涉及到單庫的丟數據場景、主從的丟數據場景以及MySQL內部XA事務原理等,相對還比較複雜,有點難以理解。

     只有當咱們瞭解了這些丟數據的場景,才能更好的去學習, 並解決這些問題。

     

     根據分佈式領域的CAP理論(Consistency、Availability、Partition tolerance),任何的分佈式系統只能同時知足2點,沒辦法三者兼顧。MySQL的主從環境知足Availability,且主從互不干擾,所以知足Partition tolerance,可是不知足Consistency,若是須要知足Consistency,則確定會失去Partition tolerance,所以實現100%高可用性的MySQL主從架構仍是很是困難的。只能經過一些設計去犧牲部分特性去知足另外的特性。

相關文章
相關標籤/搜索