不少企業選擇MySQL都會擔憂它的數據丟失問題,從而選擇Oracle,可是其實並不十分清楚什麼狀況下,各類緣由致使MySQL會丟失部分數據。本文不討論Oracle和MySQL的優劣,僅僅關注MySQL丟失數據的幾種狀況。但願可以拋磚引玉,讓各位MySQL大牛們梳理出MySQL最安全或者性價比合適的適合各類應用場景的方案。html
通常咱們但願把一系列的數據做爲一個原子操做,這樣的話,這一系列操做,要麼提交,要麼所有回滾掉。java
當咱們提交一個事務,數據庫要麼告訴咱們事務提交成功了,要麼告訴咱們提交失敗。mysql
數據庫爲了效率等緣由,數據只保存在內存中,沒有真正的寫入到磁盤上去。若是數據庫響應爲「提交成功」,可是因爲數據庫掛掉,操做系統,數據庫主機等任何問題致使此次「提交成功」的事務對數據庫的修改沒有生效,那麼咱們認爲這個事務的數據丟失了。這個對銀行或者支付寶這種業務場景來講是不能接受的。因此,保證數據不丟失也是數據庫選擇的一個重要衡量指標linux
mysql的架構和普通的數據庫架構最大的差別在於它使用插件式的存儲引擎。數據的存取由存儲引擎負責。要了解MySQL數據丟失的問題就須要從MySQL server層和InnoDB目前最流行的支持事務的存儲引擎分別來分析了。sql
首先,咱們來看一下InnoDB事務數據丟失的狀況。數據庫
InnoDB的事務提交須要寫入undo log,redo log,以及真正的數據頁。專業的介紹能夠參考 丁奇 和 雲華 的兩篇文章。咱們這裏通俗一點簡單介紹一下。緩存
InnoDB跟Oracle很是相似,使用日誌先行的策略,將數據的變動在內存中完成,而且將事務記錄成redo,轉換爲順序IO高效的提交事務。這裏日誌先行,也就是說,日誌記錄到數據庫之後,對應的事務就能夠返回給用戶,表示事務完成。可是實際上,這個數據可能還只在內存中修改完成,並無刷到磁盤上去,俗稱「尚未落地」。內存是易失的,若是在數據「落地」以前,機器掛了,那麼這部分數據就丟失了。而數據庫怎麼保證這些數據仍是可以找回來列?不然,用戶提交了一個事務,數據庫響應請求並回應爲事務「提交成功」,數據庫重啓之後,這部分修改數據的卻回到了事務提交以前的狀態。安全
InnoDB和Oracle都是利用redo來保證數據一致性的。若是你有從數據庫新建一直到數據庫掛掉的全部redo,那麼你能夠將數據完完整整的從新build出來。可是這樣的話,速度確定很慢。因此通常每隔一段時間,數據庫會作一個checkpoint的操做,作checkpoint的目的就是爲了讓在該時刻以前的全部數據都"落地"。這樣的話,數據庫掛了,內存中的數據丟了,不用從最原始的位置開始恢復,而只須要從最新的checkpoint來恢復。將已經提交的全部事務變動到具體的數據塊中,將那些未提交的事務回滾掉。架構
這樣的話,保證事務的redo日誌刷到磁盤就成了事務數據是否丟失的關鍵。而InnoDB爲了保證日誌的刷寫的高效,使用了內存的log buffer,另外,因爲InnoDB大部分狀況下使用的是文件系統,(linux文件系統自己也是有buffer的)而不是直接使用物理塊設備,這樣的話就有兩種丟失日誌的可能性:日誌保存在log_buffer中,機器掛了,對應的事務數據就丟失了;日誌從log buffer刷到了linux文件系統的buffer,機器掛掉了,對應的事務數據就丟失了。固然,文件系統的緩存刷新到硬件設備,還有可能被raid卡的緩存,甚至是磁盤自己的緩存保留,而不是真正的寫到磁盤介質上去了。這個就不在咱們此次討論的範圍內了。併發
InnoDB的日誌你還能夠參考這篇 文章
因此InnoDB有一個特別的參數用於設置這兩個緩存的刷新: innodb_flush_log_at_trx_commit。
默認,innodb_flush_log_at_trx_commit=1,表示在每次事務提交的時候,都把log buffer刷到文件系統中去,而且調用文件系統的「flush」操做將緩存刷新到磁盤上去。這樣的話,數據庫對IO的要求就很是高了,若是底層的硬件提供的IOPS比較差,那麼MySQL數據庫的併發很快就會因爲硬件IO的問題而沒法提高。
爲了提升效率,保證併發,犧牲必定的數據一致性。innodb_flush_log_at_trx_commit還能夠設置爲0和2。
innodb_flush_log_at_trx_commit=0時,每隔一秒把log buffer刷到文件系統中去,而且調用文件系統的「flush」操做將緩存刷新到磁盤上去。這樣的話,可能丟失1秒的事務數據。
innodb_flush_log_at_trx_commit=2時,在每次事務提交的時候會把log buffer刷到文件系統中去,可是每隔一秒調用文件系統的「flush」操做將緩存刷新到磁盤上去。若是隻是MySQL數據庫掛掉了,因爲文件系統沒有問題,那麼對應的事務數據並無丟失。只有在數據庫所在的主機操做系統損壞或者忽然掉電的狀況下,數據庫的事務數據可能丟失1秒之類的事務數據。這樣的好處就是,減小了事務數據丟失的機率,而對底層硬件的IO要求也沒有那麼高(log buffer寫到文件系統中,通常只是從log buffer的內存轉移的文件系統的內存緩存中,對底層IO沒有壓力)。MySQL 5.6.6之後,這個「1秒」的刷新還能夠用innodb_flush_log_at_timeout 來控制刷新間隔。
在大部分應用環境中,應用對數據的一致性要求並無那麼高,因此不少MySQL DBA會設置innodb_flush_log_at_trx_commit=2,這樣的話,數據庫就存在丟失最多1秒的事務數據的風險。
引用 應元 的一個圖以下:
MySQL相比其餘數據庫更適用於互聯網的其中一個重要特性就是MySQL的複製。對於互聯網這種須要提供7*24小時不間斷的服務的要求,MySQL提供異步的數據同步機制。利用這種複製同步機制,當數據庫主庫沒法提供服務時,應用能夠快速切換到跟它保持同步的一個備庫中去。備庫繼續爲應用提供服務,從而不影響應用的可用性。
這裏有一個關鍵的問題,就是應用切換到備庫訪問,備庫的數據須要跟主庫的數據一致才能保證不丟失數據。因爲目前MySQL尚未提供全同步的主備複製解決方案因此這裏也是可能存在數據丟失的狀況。
目前MySQL提供兩種主備同步的方式:異步(asynchronous)和半同步(Semi-sync)
MySQL複製的原理簡介以下:MySQL主庫在事務提交時寫binlog,並經過sync_binlog參數來控制binlog刷新到磁盤「落地」。而備庫經過IO線程從主庫拉取binlog,並記錄到本地的relay log中;由本地的SQL線程再將relay log中的數據應用到本地數據庫中。
異步的方式下,幾個線程都是獨立的,相互不依賴。
而在半同步的狀況下,主庫的事務提交須要保證至少有一個備庫的IO線程已經拉到了數據,這樣保證了至少有一個備庫有最新的事務數據,避免了數據丟失。這裏稱爲半同步,是由於主庫並不要求SQL線程已經執行完成了這個事務。
半同步在MySQL 5.5纔開始提供,而且可能引發併發和效率的一系列問題,好比只有一個備庫,備庫掛掉了,那麼主庫在事務提交10秒(rpl_semi_sync_master_timeout控制)後,纔會繼續,以後變成傳統的異步方式。因此目前在生產環境下使用半同步的比較少。
在異步方式下,如何保證數據儘可能不丟失就成了主要問題。這個問題其實就是如何保證數據庫的binlog不丟失,儘快將binlog落地,這樣就算數據庫掛掉了,咱們還能夠經過binlog來將丟失的部分數據手工同步到備庫上去(MHA會自動抽取缺失的部分補全備庫)。
圖示以下:
這個問題就跟上一個innodb_flush_log_at_trx_commit的問題相似了。MySQL提供一個sync_binlog參數來控制數據庫的binlog刷到磁盤上去。雖然binlog也有binlog cache,可是MySQL並無控制binlog cache同步到文件系統緩存的相關考慮。因此咱們這裏不涉及binlog cache。
默認,sync_binlog=0,表示MySQL不控制binlog的刷新,由文件系統本身控制它的緩存的刷新。
若是sync_binlog>0,表示每sync_binlog次事務提交,MySQL調用文件系統的刷新操做將緩存刷下去。最安全的就是sync_binlog=1了,表示每次事務提交,MySQL都會把binlog刷下去。這樣的話,在數據庫所在的主機操做系統損壞或者忽然掉電的狀況下,系統纔有可能丟失1個事務的數據。可是binlog雖然是順序IO,可是設置sync_binlog=1,多個事務同時提交,一樣很大的影響MySQL和IO性能。雖然能夠經過group commit的補丁緩解,可是刷新的頻率太高對IO的影響也很是大。
因此不少MySQL DBA設置的sync_binlog並非最安全的1,而是100或者是0。這樣犧牲必定的一致性,能夠得到更高的併發和性能。
最後咱們須要討論一下上述兩個參數對應的redolog和 binlog協同的問題。這兩個log都影響數據丟失,可是他們分別在InnoDB和MySQL server層維護。因爲一個事務可能使用兩種事務引擎,因此MySQL用兩段式事務提交來協調事務提交。咱們先簡單瞭解一下兩段式事務提交的過程
第一階段:
首先,協調者在自身節點的日誌中寫入一條的日誌記錄,而後全部參與者發送消息prepare T,詢問這些參與者(包括自身),是否可以提交這個事務;
參與者在接受到這個prepare T 消息之後,會根據自身的狀況,進行事務的預處理,若是參與者可以提交該事務,則會將日誌寫入磁盤,並返回給協調者一個ready T信息,同時自身進入預提交狀態狀態;若是不能提交該事務,則記錄日誌,並返回一個not commit T信息給協調者,同時撤銷在自身上所作的數據庫改;
參與者可以推遲發送響應的時間,但最終仍是須要發送的。
第二階段:
協調者會收集全部參與者的意見,若是收到參與者發來的not commit T信息,則標識着該事務不能提交,協調者會將Abort T 記錄到日誌中,並向全部參與者發送一個Abort T 信息,讓全部參與者撤銷在自身上全部的預操做;
若是協調者收到全部參與者發來prepare T信息,那麼協調者會將Commit T日誌寫入磁盤,並向全部參與者發送一個Commit T信息,提交該事務。若協調者遲遲未收到某個參與者發來的信息,則認爲該參與者發送了一個VOTE_ABORT信息,從而取消該事務的執行。
參與者接收到協調者發來的Abort T信息之後,參與者會終止提交,並將Abort T 記錄到日誌中;若是參與者收到的是Commit T信息,則會將事務進行提交,並寫入記錄
通常狀況下,兩階段提交機制都能較好的運行,當在事務進行過程當中,有參與者宕機時,他重啓之後,能夠經過詢問其餘參與者或者協調者,從而知道這個事務到底提交了沒有。固然,這一切的前提都是各個參與者在進行每一步操做時,都會事先寫入日誌。
具體的介紹能夠參考 《事務和兩階段提交》 以及 《分佈式事務設計-兩階段提交》
innodb_support_xa能夠開關InnoDB的xa兩段式事務提交。默認狀況下,innodb_support_xa=true,支持xa兩段式事務提交。此時MySQL首先要求innodb prepare,對應的redolog 將寫入log buffer;若是有其餘的引擎,其餘引擎也須要作事務提交的prepare,而後MySQL server將binlog將寫入;並通知各事務引擎真正commit;InnoDB將commit標誌寫入,完成真正的提交,響應應用程序爲提交成功。這個過程當中任何出錯將致使事務回滾,響應應用程序爲提交失敗。也就是說,在這種狀況下,基本不會出錯。
可是因爲xa兩段式事務提交致使多餘flush等操做,性能影響會達到10%,全部爲了提升性能,有些DBA會設置innodb_support_xa=false。這樣的話,redolog和binlog將沒法同步,可能存在事務在主庫提交,可是沒有記錄到binlog的狀況。這樣也有可能形成事務數據的丟失。
綜上,咱們列舉了影響InnoDB數據丟失的參數innodb_flush_log_at_trx_commit,影響MySQL複製數據丟失的sync_binlog,以及因爲MySQL和InnoDB須要協調而可能致使數據丟失的參數innodb_support_xa。