1. 程式人生 > >mysql如何保證數據一致性

mysql如何保證數據一致性

數據 丟失

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

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

1.1.InnoDB丟數據

InnoDB支持事務,同Oracle類似,事務提交需要寫redo、undo。采用日誌先行的策略,將數據的變更在內存中完成,並且將事務記錄成redo,順序的寫入redo日誌中,即表示該事務已經完成,就可以返回給客戶已提交的信息。但是實際上被更改的數據還在內存中,並沒有刷新到磁盤,即還沒有落地,當達到一定的條件,會觸發checkpoint,將內存中的數據(page)合並寫入到磁盤,這樣就減少了離散寫、IOPS,提高性能。

在這個過程中,如果服務器宕機了,內存中的數據丟失,當重啟後,會通過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的實時寫性能更好?線程與進程?後面還需要研究

大家可以考慮一下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的數據應用到本地數據庫,如下圖所示:

技術分享

從上圖我們可以看到,在主從環境中,增加了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中

技術分享

如果在步驟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主從架構還是非常困難的。只能通過一些設計去犧牲部分特性去滿足另外的特性。


本文出自 “linux運維” 博客,謝絕轉載!

mysql如何保證數據一致性