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事務的大致流程:
事務提交後,InnoDB存儲引擎會先做一個PREPARE操作,將事務的XID寫入到redo日誌中。
寫binlog日誌
再將該事務的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如何保證數據一致性