讨论MySQL丢失数据的几种情况
1. 問題定義
一般我們希望把一系列的數據作為一個原子操作,這樣的話,這一系列操作,要么提交,要么全部回滾掉。
????當我們提交一個事務,數據庫要么告訴我們事務提交成功了,要么告訴我們提交失敗。
數據庫為了效率等原因,數據只保存在內存中,沒有真正的寫入到磁盤上去。如果數據庫響應為“提交成功”,但是由于數據庫掛掉,操作系統,數據庫主機等任何問題導致這次“提交成功”的事務對數據庫的修改沒有生效,那么我們認為這個事務的數據丟失了。這個對銀行或者支付寶這種業務場景來說是不能接受的。所以,保證數據不丟失也是數據庫選擇的一個重要衡量指標
mysql的架構和普通的數據庫架構最大的差異在于它使用插件式的存儲引擎。數據的存取由存儲引擎負責。要了解MySQL數據丟失的問題就需要從MySQL server層和InnoDB目前最流行的支持事務的存儲引擎分別來分析了。
2. INNODB事務數據丟失
首先,我們來看一下InnoDB事務數據丟失的情況。
2.1. INNODB事務基本原理
InnoDB的事務提交需要寫入undo log,redo log,以及真正的數據頁。InnoDB跟Oracle非常類似,使用日志先行的策略,將數據的變更在內存中完成,并且將事務記錄成redo,轉換為順序IO高效的提交事務。這里日志先行,也就是說,日志記錄到數據庫以后,對應的事務就可以返回給用戶,表示事務完成。但是實際上,這個數據可能還只在內存中修改完成,并沒有刷到磁盤上去,俗稱“還沒有落地”。內存是易失的,如果在數據“落地”之前,機器掛了,那么這部分數據就丟失了。而數據庫怎么保證這些數據還是能夠找回來列?否則,用戶提交了一個事務,數據庫響應請求并回應為事務“提交成功”,數據庫重啟以后,這部分修改數據的卻回到了事務提交之前的狀態。
2.2. INNODB事務崩潰恢復基本原理
InnoDB和Oracle都是利用redo來保證數據一致性的。如果你有從數據庫新建一直到數據庫掛掉的所有redo,那么你可以將數據完完整整的重新build出來。但是這樣的話,速度肯定很慢。所以一般每隔一段時間,數據庫會做一個checkpoint的操作,做checkpoint的目的就是為了讓在該時刻之前的所有數據都"落地"。這樣的話,數據庫掛了,內存中的數據丟了,不用從最原始的位置開始恢復,而只需要從最新的checkpoint來恢復。將已經提交的所有事務變更到具體的數據塊中,將那些未提交的事務回滾掉。
2.3. INNODB REDO日志
這樣的話,保證事務的redo日志刷到磁盤就成了事務數據是否丟失的關鍵。而InnoDB為了保證日志的刷寫的高效,使用了內存的log buffer,另外,由于InnoDB大部分情況下使用的是文件系統,(linux文件系統本身也是有buffer的)而不是直接使用物理塊設備,這樣的話就有兩種丟失日志的可能性:日志保存在log_buffer中,機器掛了,對應的事務數據就丟失了;日志從log buffer刷到了linux文件系統的buffer,機器掛掉了,對應的事務數據就丟失了。當然,文件系統的緩存刷新到硬件設備,還有可能被raid卡的緩存,甚至是磁盤本身的緩存保留,而不是真正的寫到磁盤介質上去了。
2.4. INNODB_FLUSH_LOG_AT_TRX_COMMIT
所以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秒的事務數據的風險。
如下圖所示:
3. 數據庫復制導致數據丟失
MySQL相比其他數據庫更適用于互聯網的其中一個重要特性就是MySQL的復制。對于互聯網這種需要提供7*24小時不間斷的服務的要求,MySQL提供異步的數據同步機制。利用這種復制同步機制,當數據庫主庫無法提供服務時,應用可以快速切換到跟它保持同步的一個備庫中去。備庫繼續為應用提供服務,從而不影響應用的可用性。
這里有一個關鍵的問題,就是應用切換到備庫訪問,備庫的數據需要跟主庫的數據一致才能保證不丟失數據。由于目前MySQL還沒有提供全同步的主備復制解決方案所以這里也是可能存在數據丟失的情況。
目前MySQL提供兩種主備同步的方式:異步(asynchronous)和半同步(Semi-sync)
3.1. MYSQL復制原理簡介
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會自動抽取缺失的部分補全備庫)。
3.2. SYNC_BINLOG
這個問題就跟上一個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。這樣犧牲一定的一致性,可以獲得更高的并發和性能。
4. MYSQL和INNODB協同
4.1. 兩段式事務提交
最后我們需要討論一下上述兩個參數對應的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信息,則會將事務進行提交,并寫入記錄
一般情況下,兩階段提交機制都能較好的運行,當在事務進行過程中,有參與者宕機時,他重啟以后,可以通過詢問其他參與者或者協調者,從而知道這個事務到底提交了沒有。當然,這一切的前提都是各個參與者在進行每一步操作時,都會事先寫入日志。
4.2. INNODB_SUPPORT_XA
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。
轉載于:https://blog.51cto.com/xiaoze/1607601
總結
以上是生活随笔為你收集整理的讨论MySQL丢失数据的几种情况的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 总结 XSS 与 CSRF 两种跨站攻击
- 下一篇: UIDocumentInteractio