掃描下方二維碼或者微信搜索公衆號
菜鳥飛呀飛
,便可關注微信公衆號,閱讀更多Spring源碼分析
、Java併發編程
和Netty源碼系列
文章。mysql
在開始閱讀本文以前,能夠先思考一下下面兩個問題。sql
在 MySQL 5.5 之後,默認的存儲引擎爲 InnoDB,且只有 InnoDB 引擎支持事務和數據崩潰恢復,所以本文全部內容均是基於 InnoDB 存儲引擎爲前提。shell
MySQL 在更新數據時,爲了減小磁盤的隨機 IO,所以並不會直接更新磁盤上的數據,而是先更新 Buffer Pool 中緩存頁的數據,等到合適的時間點,再將這個緩存頁持久化到磁盤。而 Buffer Pool 中全部緩存頁都是處於內存當中的,當 MySQL 宕機或者機器斷電,內存中的數據就會丟失,所以 MySQL 爲了防止緩存頁中的數據在更新後出現數據丟失的現象,引入了 redo log 機制。數據庫
當進行增刪改操做時,MySQL 會在更新 Buffer Pool 中的緩存頁數據時,會記錄一條對應操做的 redo log 日誌,這樣若是出現 MySQL 宕機或者斷電時,若是有緩存頁的數據還沒來得及刷入磁盤,那麼當 MySQL 從新啓動時,能夠根據 redo log 日誌文件,進行數據重作,將數據恢復到宕機或者斷電前的狀態,保證了更新的數據不丟失,所以 redo log 又叫作重作日誌。它的本質是保證事務提交後,更新的數據不丟失。編程
與 binlog 不一樣的是,redo log 中記錄的是物理日誌,是 InnoDB 引擎記錄的,而 binlog 記錄的是邏輯日誌,是 MySQL 的 Server 層記錄的。什麼意思呢?binlog 中記錄的是 SQL 語句(實際上並不必定爲 SQL 語句,這與 binlog 的格式有關,若是指定的是 STATEMENT 格式,那麼 binlog 中記錄的就是 SQL 語句),也就是邏輯日誌;而 redo log 中則記錄的是對磁盤上的某個表空間的某個數據頁的某一行數據的某個字段作了修改,修改後的值爲多少,它記錄的是對物理磁盤上數據的修改,所以稱之爲物理日誌。緩存
redo log 日誌文件是持久化在磁盤上的,磁盤上能夠有多個 redo log 文件,MySQL 默認有 2 個 redo log 文件,每一個文件大小爲 48MB,這兩個文件默認存放在 MySQL 數據目錄的文件夾下,這兩個文件分別爲 ib_logfile0 和 ib_logfile1。(本人電腦上安裝的 MySQL 時,指定存放數據的目錄是:/usr/local/mysql/data,所以這兩個 redo log 文件所在的磁盤路徑分別是:/usr/local/mysql/data/ib_logfile0 和/usr/local/mysql/data/ib_logfile1)。能夠經過以下命令來查看 redo log 文件相關的配置。安全
show variables like 'innodb_log%'
複製代碼
查詢結果如圖。服務器
當一條 SQL 更新完 Buffer Pool 中的緩存頁後,就會記錄一條 redo log 日誌,前面提到了 redo log 日誌是存儲在磁盤上的,那麼此時是否是立馬就將 redo log 日誌寫入磁盤呢?顯然不是的,而是先寫入一個叫作 redo log buffer 的緩存中,redo log buffer 是一塊不一樣於 buffer pool 的內存緩存區,在 MySQL 啓動的時候,向內存中申請的一塊內存區域,它是 redo log 日誌緩衝區,默認大小是 16MB,由參數 innodb_log_buffer_size 控制(前面的截圖中能夠看到)。微信
redo log buffer 內部又能夠劃分爲許多 redo log block,每一個 redo log block 大小爲 512 字節。咱們寫入的 redo log 日誌,最終其實是先寫入在 redo log buffer 的 redo log block 中,而後在某一個合適的時間點,將這條 redo log 所在的 redo log block 刷入到磁盤中。併發
這個合適的時間點到底是何時呢?
前面介紹了 redo log 相關的基礎知識,下面來看下 MySQL 到底是如何來保證數據不丟失的。
將 redo log 日誌標記爲 prepare 狀態和 commit 狀態,這種作法稱之爲兩階段事務提交,它能保證事務在提交後,數據不丟失。爲何呢?redo log 在進行數據重作時,只有讀到了 commit 標識,纔會認爲這條 redo log 日誌是完整的,纔會進行數據重作,不然會認爲這個 redo log 日誌不完整,不會進行數據重作。
例如,若是在 redo log 處於 prepare 狀態後,buffer pool 中的緩存頁(髒頁)也還沒來得及刷入到磁盤,寫完 biglog 後就出現了宕機或者斷電,此時提交的事務是失敗的,那麼在 MySQL 重啓後,進行數據重作時,在 redo log 日誌中因爲該事務的 redo log 日誌沒有 commit 標識,那麼就不會進行數據重作,磁盤上數據仍是原來的數據,也就是事務沒有提交,這符合咱們的邏輯。
實際上要嚴格保證數據不丟失,必須得保證 innodb_flush_log_at_trx_commit 配置爲 1。
若是配置成 0,則 redo log 即便標記爲 commit 狀態了,因爲此時 redo log 處於 redo log buffer 中,若是斷電,redo log buffer 內存中的數據會丟失,此時若是剛好 buffer pool 中的髒頁也尚未刷新到磁盤,而 redo log 也丟失了,因此在 MySQL 重啓後,因爲丟失了一條 redo log,所以就會丟失一條 redo log 對應的重作日誌,這樣斷電前提交的那一次事務的數據也就丟失了。
若是配置成 2,則事務提交時,會將 redo log buffer(其實是這次事務所對應的那條 redo log 所在的 redo log block )寫入磁盤,可是操做系統一般都會存在 os cache,因此這時候的寫只是將數據寫入到了 os cache,若是機器斷電,數據依然會丟失。
而若是配置成 1,則表示事務提交時,就將對應的 redo log block 寫入到磁盤,同時調用 fsync,fsync 會將數據強制從 os cache 中刷入到磁盤中,所以數據不會丟失。
從效率上來講,0 的效率最高,由於不涉及到磁盤 IO,可是會丟失數據;而 1 的效率最低,可是最安全,不會丟失數據。2 的效率居中,會丟失數據。在實際的生產環境中,一般要求是的是「雙 1 配置」,即將 innodb_flush_log_at_trx_commit 設置爲 1,另一個 1 指的是寫 binlog 時,將 sync_binlog 設置爲 1,這樣 binlog 的數據就不會丟失(後面的文章中會分析 binlog 相關的內容)。
看到這裏,有人可能會想,既然生產環境通常建議將 innodb_flush_log_at_trx_commit 設置爲 1,也就是說每次更新數據時,最終仍是要將 redo log 寫入到磁盤,也就是仍是會發生一次磁盤 IO,而我爲何不直接中止使用 redo log,而在每次更新數據時,也不要直接更新內存了,直接將數據更新到磁盤,這樣也是發生了一次磁盤 IO,何須引入 redo log 這一機制呢?
首先引入 redo log 機制是十分必要的。由於寫 redo log 時,咱們將 redo log 日誌追加到文件末尾,雖然也是一次磁盤 IO,可是這是順序寫操做(不須要移動磁頭);而對於直接將數據更新到磁盤,涉及到的操做是將 buffer pool 中緩存頁寫入到磁盤上的數據頁上,因爲涉及到尋找數據頁在磁盤的哪一個地方,這個操做發生的是隨機寫操做(須要移動磁頭),相比於順序寫操做,磁盤的隨機寫操做性能消耗更大,花費的時間更長,所以 redo log 機制更優,能提高 MySQL 的性能。
從另外一方面來說,一般一次更新操做,咱們每每只會涉及到修改幾個字節的數據,而若是由於僅僅修改幾個字節的數據,就將整個數據頁寫入到磁盤(不管是磁盤仍是 buffer pool,他們管理數據的單位都是以頁爲單位),這個代價未免也太了(每一個數據頁默認是 16KB),而一條 redo log 日誌的大小可能就只有幾個字節,所以每次磁盤 IO 寫入的數據量更小,那麼耗時也會更短。 綜合來看,redo log 機制的引入,在提升 MySQL 性能的同時,也保證了數據的可靠性。
最後解答下文章開頭的兩個問題。