天天看點

事務已送出,資料卻丢了,趕緊檢查下這個配置!!! | 資料庫系列

有個星球水友提問:沈老師,我們有一次MySQL崩潰,重新開機後發現有些已經送出的事務對資料的修改丢失了,不是說事務能保證ACID特性麼,想問下什麼情況下可能導緻“事務已經送出,資料卻丢失”呢? 這個問題有點複雜,且容我系統性梳理下思路,先從redo log說起吧。畫外音:水友問的是MySQL,支援事務的是InnoDB,本文以InnoDB為例展開叙述,其他資料庫不是很了解,但估計原理是相同的。 為什麼要有redo log?事務送出後,必須将事務對資料頁的修改刷(fsync)到磁盤上,才能保證事務的ACID特性。 這個刷盤,是一個随機寫,随機寫性能較低,如果每次事務送出都刷盤,會極大影響資料庫的性能。 随機寫性能差,有什麼優化方法呢?架構設計中有兩個常見的優化方法:(1)先寫日志(write log first),将随機寫優化為順序寫;(2)将每次寫優化為批量寫;這兩個優化,資料庫都用上了。 先說第一個優化,将對資料的修改先順序寫到日志裡,這個日志就是redo log。 假如某一時刻,資料庫崩潰,還沒來得及将資料頁刷盤,資料庫重新開機時,會重做redo log裡的内容,以保證已送出事務對資料的影響被刷到磁盤上。 一句話,redo log是為了保證已送出事務的ACID特性,同時能夠提高資料庫性能的技術。 既然redo log能保證事務的ACID特性,那為什麼還會出現,水友提問中出現的“資料庫奔潰,丢資料”的問題呢?一起看下redo log的實作細節。 redo log的三層架構?

事務已送出,資料卻丢了,趕緊檢查下這個配置!!! | 資料庫系列
花了一個醜圖,簡單說明下redo log的三層架構:

  • 粉色,是InnoDB的一項很重要的記憶體結構(In-Memory Structure),日志緩沖區(Log Buffer),這一層,是MySQL應用程式使用者态
  • 屎黃色,是作業系統的緩沖區(OS cache),這一層,是OS核心态
  • 藍色,是落盤的日志檔案

 redo log最終落盤的步驟如何?首先,事務送出的時候,會寫入Log Buffer,這裡調用的是MySQL自己的函數WriteRedoLog; 接着,隻有當MySQL發起系統調用寫檔案write時,Log Buffer裡的資料,才會寫到OS cache。注意,MySQL系統調用完write之後,就認為檔案已經寫完,如果不flush,什麼時候落盤,是作業系統決定的;畫外音:有時候打日志,明明printf了,tail -f卻看不到,就是這個原因,這個細節在《明明列印到檔案了,為啥tail -f看不到》一文裡說過,此處不再展開。 最後,由作業系統(當然,MySQL也可以主動flush)将OS cache裡的資料,最終fsync到磁盤上; 作業系統為什麼要緩沖資料到OS cache裡,而不直接刷盤呢?這裡就是将“每次寫”優化為“批量寫”,以提高作業系統性能。 資料庫為什麼要緩沖資料到Log Buffer裡,而不是直接write呢?這也是“每次寫”優化為“批量寫”思路的展現,以提高資料庫性能。畫外音:這個優化思路,非常常見,高并發的MQ落盤,高并發的業務資料落盤,都可以使用。 redo log的三層架構,MySQL做了一次批量寫優化,OS做了一次批量寫優化,确實能極大提升性能,但有什麼副作用嗎?畫外音:有優點,必有缺點。 這個副作用,就是可能丢失資料:(1)事務送出時,将redo log寫入Log Buffer,就會認為事務送出成功;

(2)如果寫入Log Buffer的資料,write入OS cache之前,資料庫崩潰,就會出現資料丢失;

(3)如果寫入OS cache的資料,fsync入磁盤之前,作業系統奔潰,也可能出現資料丢失;畫外音:如上文所說,應用程式系統調用完write之後(不可能每次write後都立刻flush,這樣寫日志很蠢),就認為寫成功了,作業系統何時fsync,應用程式并不知道,如果作業系統崩潰,資料可能丢失。 任何脫離業務的技術方案都是耍流氓:(1)有些業務允許低效,但不允許一丁點資料丢失;(2)有些業務必須高性能高吞吐,能夠容忍少量資料丢失;MySQL是如何折衷的呢? MySQL有一個參數:innodb_flush_log_at_trx_commit能夠控制事務送出時,刷redo log的政策。 目前有三種政策

事務已送出,資料卻丢了,趕緊檢查下這個配置!!! | 資料庫系列

政策一:最佳性能(innodb_flush_log_at_trx_commit=0)

每隔一秒,才将Log Buffer中的資料批量write入OS cache,同時MySQL主動fsync。這種政策,如果資料庫奔潰,有一秒的資料丢失。 政策二:強一緻(innodb_flush_log_at_trx_commit=1)每次事務送出,都将Log Buffer中的資料write入OS cache,同時MySQL主動fsync。這種政策,是InnoDB的預設配置,為的是保證事務ACID特性。 政策三:折衷(innodb_flush_log_at_trx_commit=2)每次事務送出,都将Log Buffer中的資料write入OS cache;每隔一秒,MySQL主動将OS cache中的資料批量fsync。畫外音:磁盤IO次數不确定,因為作業系統的fsync頻率并不是MySQL能控制的。這種政策,如果作業系統奔潰,最多有一秒的資料丢失。畫外音:因為OS也會fsync,MySQL主動fsync的周期是一秒,是以最多丢一秒資料。

事務已送出,資料卻丢了,趕緊檢查下這個配置!!! | 資料庫系列

講了這麼多,回到水友的提問上來,資料庫崩潰,重新開機後丢失了資料,有很大的可能,是将innodb_flush_log_at_trx_commit參數設定為0了,這位水友最好和DBA一起檢查一下InnoDB的配置。

 可能有水友要問,高并發的業務,InnoDB運用哪種刷盤政策最合适?

高并發業務,行業最佳實踐,是使用第三種折衷配置(=2),這是因為:(1)配置為2和配置為0,性能差異并不大,因為将資料從Log Buffer拷貝到OS cache,雖然跨越使用者态與核心态,但畢竟隻是記憶體的資料拷貝,速度很快;(2)配置為2和配置為0,安全性差異巨大,作業系統崩潰的機率相比MySQL應用程式崩潰的機率,小很多,設定為2,隻要作業系統不奔潰,也絕對不會丢資料。

總結

一、為了保證事務的ACID特性,理論上每次事務送出都應該刷盤,但此時效率很低,有兩種優化方向:

(1)随機寫優化為順序寫;

(2)每次寫優化為批量寫;

二、redo log是一種順序寫,它有三層架構:

(1)MySQL應用層:Log Buffer

(2)OS核心層:OS cache

(3)OS檔案:log file

三、為了滿足不用業務對于吞吐量與一緻性的需求,MySQL事務送出時刷redo log有三種政策:

(1)0:每秒write一次OS cache,同時fsync刷磁盤,性能好;

(2)1:每次都write入OS cache,同時fsync刷磁盤,一緻性好;

(3)2:每次都write入OS cache,每秒fsync刷磁盤,折衷;

四、高并發業務,行業内的最佳實踐,是:

innodb_flush_log_at_trx_commit=2

知其然,知其是以然,希望大家有收獲。

本文轉自“架構師之路”公衆号,58沈劍提供。