大綱
1.更新語句在MySQL中是如何執行的
2.重要的記憶體結構—Buffer Pool緩衝池
3.undo日誌檔案如何讓更新的資料可以回滾
4.更新Buffer Pool緩衝池中的快取資料
5.Redo Log Buffer如何避免當機時資料丟失
6.如果還沒提交事務時MySQL當機了怎麼辦
7.提交事務時將redo日誌寫入磁碟中
8.redo日誌刷盤策略的選擇和建議
9.MySQL的redo log和binlog對比
10.提交事務時同時也會寫入binlog
11.binlog日誌的刷盤策略分析
12.基於binlog的redo log完成事務的提交
13.在redo日誌中寫入commit標記的意義
14.後臺IO執行緒隨機將記憶體更新後的髒資料刷盤
15.InnoDB儲存引擎的架構原理總結
這裡主要介紹MySQL的資料快取機制
1.更新語句在MySQL中是如何執行的
假設有一條如下這樣的SQL語句,那麼這條語句是如何執行的呢?
update users set name = 'xxx' where id = 1;
首先Java系統會透過一個資料庫連線將該SQL語句傳送到MySQL上,然後經過SQL介面、查詢解析器、查詢最佳化器、執行器環節,在解析出了SQL語句、生成了執行計劃後,再由執行器呼叫InnoDB儲存引擎的介面去執行生成的執行計劃。
下面介紹InnoDB儲存引擎裡的架構設計,以及如何基於InnoDB儲存引擎完成一條更新語句的執行。
2.重要的記憶體結構—Buffer Pool緩衝池
InnoDB有一個非常重要、放在記憶體裡的元件,就是緩衝池(Buffer Pool)。緩衝池會快取很多磁碟檔案資料,以便在查詢時不用去查磁碟。如下圖示:
所以當InnoDB儲存引擎要執行更新語句時:比如對"id=1"這一行資料,會先判斷"id=1"這一行資料是否在緩衝池裡。如果不在,則直接從磁碟裡載入到緩衝池裡,且對這行記錄加獨佔鎖。
3.undo日誌檔案如何讓更新的資料可以回滾
假設"id=1"這行資料的name原來是"zhangsan",現在要更新為"xxx"。那麼InnoDB得先把原值"zhangsan"和"id=1"寫入到undo日誌檔案中。
Java系統在執行一條SQL更新語句時,要是它在一個事務裡,那麼事務提交前是可以對資料進行回滾的。所以考慮到可能要回滾資料,InnoDB會把更新前的值寫入undo日誌檔案。如下圖示:
4.更新Buffer Pool緩衝池中的快取資料
當InnoDB把要更新的那行記錄從磁碟檔案載入到了緩衝池,同時對它加完鎖,而且還把更新前的舊值寫入undo日誌檔案後,InnoDB就可以正式開始更新這行記錄了。
更新的時候,會先更新緩衝池中的記錄,此時這個資料就是髒資料了。所謂的更新記憶體緩衝池裡的資料,意思就是把記憶體裡的"id=1"這行資料的name欄位修改為"xxx"。
為什麼說此時這行資料是髒資料呢?因為這時磁碟上"id=1"這行資料的name欄位還是"zhangsan",但記憶體裡這行資料已經被修改了,所以它是髒資料。
5.Redo Log Buffer如何避免當機時資料丟失
現在已經把記憶體裡的資料進行了修改,但是磁碟上的資料還沒修改。此時萬一MySQL所在機器當機,必然會導致記憶體裡已修改的資料丟失。這該如何處理?
為此必須把對記憶體所做的修改寫入一個Redo Log Buffer裡,Redo Log Buffer也是記憶體裡的一個緩衝區,是用來存放redo日誌的。
所謂redo日誌,就是記錄InnoDB要對資料做什麼修改。比如對"id=1"這行記錄修改name欄位的值為"xxx",就是一條redo日誌。
6.如果還沒提交事務時MySQL當機了怎麼辦
在資料庫中,哪怕執行一條SQL語句,其實也可以是一個獨立的事務。只有當事務提交後,SQL語句才算執行結束。
所以如果還沒提交事務MySQL當機了,那麼必然導致記憶體裡Buffer Pool中修改過的資料都丟失,同時寫入Redo Log Buffer中的日誌也會丟失。
此時資料丟失其實是不要緊的。因為一條更新語句只要沒提交事務,那麼就代表還沒執行成功。此時MySQL當機雖然導致記憶體裡的資料丟失,但還沒影響磁碟上的資料。
7.提交事務時將redo日誌寫入磁碟中
如果InnoDB想要提交一個事務,就會根據一定的策略把redo日誌從Redo Log Buffer中刷入到磁碟檔案裡,這個策略是透過如下這個引數來配置的:innodb_flush_log_at_trx_commit。
(1)當innodb_flush_log_at_trx_commit = 0時
那麼進行事務提交時,不會把Redo Log Buffer的資料刷入到磁碟檔案裡。這時即便提交了事務,但如果MySQL當機了,記憶體裡的資料也會全部丟失而且redo日誌裡沒有資料。
(2)當innodb_flush_log_at_trx_commit = 1時
那麼進行事務提交時,會把記憶體中的redo log刷入到磁碟檔案裡。只要事務提交成功,那麼redo log就必然在磁碟裡。哪怕此時Buffer Pool中更新過的資料還沒重新整理到磁碟,系統崩潰重啟後,也可以根據磁碟中的redo log恢復。
(3)當innodb_flush_log_at_trx_commit = 2時
那麼進行事務提交時,會把記憶體中的redo log寫入到OS Cache快取裡。OS Cache快取裡的資料可能在1秒後才會被寫入到磁碟檔案中。
在這種模式下,當InnoDB儲存引擎提交事務後,redo log可能還停留在OS Cache快取裡,還沒實際進入到磁碟檔案。而此時MySQL所在機器當機了,那麼OS Cache裡的redo log也會丟失。從而出現即便提交了事務,但是資料還是丟失了的情況。
8.redo日誌刷盤策略的選擇和建議
通常建議設定innodb_flush_log_at_trx_commit的值為1。也就是提交事務時,redo日誌必須同時刷入磁碟檔案裡。這樣可以嚴格保證提交事務後資料絕對不會丟失。
如果innodb_flush_log_at_trx_commit = 0,那麼提交事務後如果MySQL當機而此時redo日誌還沒有刷盤,則會導致記憶體裡的redo日誌丟失,記憶體更新好的資料也丟失。
如果innodb_flush_log_at_trx_commit = 2,那麼提交事務後雖然redo日誌進入了OS Cache,但OS Cache的資料此時還沒進入磁碟檔案而MySQL機器當機了,則也會導致OS Cache的redo日誌丟失。
所以一般設定redo日誌刷盤策略為1,保證事務提交後資料不會丟失。
9.MySQL的redo log和binlog對比
MySQL的redo log,是一種偏向物理性的重做日誌。因為其記錄的是:對哪個資料頁中的哪條記錄做了什麼修改。而且redo log是屬於InnoDB儲存引擎特有的日誌檔案。
MySQL的binlog,是一種偏向於邏輯性的日誌,也叫歸檔日誌。類似"對users表中id=1的一行記錄做了更新操作,更新後的值是什麼"。binlog不是InnoDB儲存引擎特有的日誌檔案,binlog是屬於MySQL資料庫層面的日誌檔案。
10.提交事務時同時也會寫入binlog
提交事務時,除了會把redo日誌寫入到磁碟檔案中,還會把這次SQL更新對應的binlog日誌寫入到磁碟檔案中。
下圖加入了執行器這個元件,它會負責和InnoDB儲存引擎進行互動:
步驟1:從磁碟載入資料到Buffer Pool快取
步驟2:寫入undo日誌
步驟3:更新Buffer Pool裡的資料
步驟4:寫入redo日誌到Redo Log Buffer
步驟5:redo日誌刷入磁碟
步驟6:寫入binlog日誌
實際上,執行器是非常核心的一個元件。執行器會與儲存引擎完成SQL語句在磁碟與記憶體層面的全部資料更新操作。
下圖把一次更新語句的執行,拆分為兩個階段。其中步驟1、2、3、4是執行更新語句的階段,而步驟5和6是屬於提交事務的階段。
11.binlog日誌的刷盤策略分析
binlog日誌也有不同的刷盤策略,透過sync_binlog引數可以控制binlog的刷盤策略,預設值是0。
(1)當sync_binlog設定為0時
表示執行器沒有直接將binlog寫入磁碟檔案,而是先將binlog寫入OS Cache快取,與redo log的innodb_flush_log_at_trx_commit的值為2一樣。
如果OS Cache裡的資料還沒寫入磁碟檔案時,MySQL所在機器當機,那麼binlog日誌也會丟失。
(2)當sync_binlog設定為1時
表示在提交事務時,執行器會把binlog直接寫入到磁碟檔案中。這樣在提交事務後即便當機,binlog也不會丟失。
12.基於binlog的redo log完成事務的提交
當MySQL把binlog寫入磁碟後,接著就會完成最終的事務提交。此時會把本次更新對應的binlog檔名稱和位置,都寫入到redo日誌裡,同時在redo日誌檔案裡寫入一個commit標記。在完成這個事情後,才算是最終完成事務的提交。
13.在redo日誌中寫入commit標記的意義
寫入commit標記是用來保持redo日誌與binlog日誌一致。也就是說,在提交事務的時候,上圖的步驟5、6、7必須都執行完畢,才算是提交了事務。
(1)如果剛完成步驟5時,redo日誌剛刷入到磁碟檔案,MySQL當機了
這時因為在redo日誌沒有最終的事務commit標記,所以此次事務不成功。因為不允許出現這樣的情況:redo日誌檔案裡有更新日誌,但是binlog日誌檔案裡沒有對應的更新日誌。否則就會導致資料不一致。
(2)如果在完成步驟6時,binlog日誌已寫入磁碟,MySQL當機了
這時因為在redo日誌沒有最終的事務commit標記,所以此次事務也失敗。所以必須要在redo日誌寫入最終的事務commit標記,才算事務提交成功。這樣redo日誌有本次更新的日誌,binlog日誌也有本次更新的日誌,從而實現redo日誌和binlog日誌完全一致。
14.後臺IO執行緒隨機將記憶體更新後的髒資料刷盤
當完成事務提交後,MySQL已把記憶體中的Buffer Pool快取資料更新了,同時磁碟裡也有redo日誌和binlog日誌,但磁碟上的資料檔案還是舊值。
這時MySQL會有一個後臺IO執行緒,在事務提交後的某個時間,隨機把記憶體Buffer Pool中修改後的髒資料刷回到磁碟上的資料檔案裡。
當IO執行緒把Buffer Pool裡修改後的髒資料刷回磁碟後,磁碟上的資料才會跟記憶體裡的資料一樣,都是修改後的值。
當IO執行緒把髒資料刷回磁碟之前,即便MySQL當機也沒關係。因為重啟後會根據redo日誌恢復提交事務時所做的修改到記憶體裡。之後IO執行緒還是會把修改後的資料刷到磁碟的資料檔案裡。
15.InnoDB儲存引擎的架構原理總結
InnoDB儲存引擎會使用Buffer Pool、Redo Log Buffer來快取資料。InnoDB儲存引擎有屬於自己的undo日誌檔案、redo日誌檔案,MySQL也有屬於自己的binlog日誌檔案。
執行更新時:會修改Buffer Pool裡的資料、寫undo日誌、寫Redo Log Buffer。
提交事務時:會把binlog刷入磁碟、在redo日誌中寫入事務標記,把redo日誌刷入磁碟。最後InnoDB後臺的IO執行緒會隨機把Buffer Pool的髒資料刷入到磁碟檔案。
(1)MySQL當機重啟如何確定是否需要從redo日誌恢復資料
MySQL當機重啟,如何確定髒資料在當機前是否已全部刷寫回磁碟檔案。
MySQL當機重啟,InnoDB會首先去檢視資料頁中LSN的數值。LSN就是InnoDB使用的一個版本標記的計數。如果資料頁中的LSN異於redo日誌的commit標記,那麼就去檢視redo日誌的LSN大小。如果資料頁的LSN值大,則說明資料頁領先redo日誌,不需要恢復,反之則需要從redo日誌中恢復。
(2)從redo日誌恢復資料時是全量恢復還是指定位置後恢復
redo日誌是劃歸於一個redo日誌組的。預設一個redo日誌組有兩個redo日誌檔案。寫redo日誌時是迴圈寫入,寫滿一個redo日誌檔案再寫另外一個。
在寫滿切換redo日誌檔案時,會觸發資料庫的檢查點checkpoint。checkpoint所做的事就是把髒頁重新整理回磁碟。
當DB重啟恢復時只需要恢復checkpoint之後的資料即可。所以redo日誌檔案大小不宜過大,不然導致恢復時需要更長的時間。redo日誌檔案大小也不宜過小,不然導致頻繁切換觸發檢測點降低效能。
(3)既然有redo日誌來保證崩潰恢復,為什麼還要有binlog日誌
binlog日誌其實就是歸檔日誌,主要用來做資料恢復的。MySQL最開始設計時只有MyISAM引擎只有binlog,不支援InnoDB。此外資料庫備份以及hadoop系統資料分析都是binlog來實現的,所以還需要binlog。
(4)redo日誌和binlog日誌的資料結構是怎樣的
redo日誌是迴圈寫,會把redo日誌分為0,1,2,3四個區間,有兩個指標。writepos指標是一邊寫一邊向後移動,checkpoint指標是一邊擦除一邊向後移動。所以redo日誌是不能儲存很多記錄的,必須持久化到磁碟中。binlog日誌是追加寫,不會覆蓋之前的日誌。
(5)binlog日誌和redo日誌是怎麼保持一致性的
binlog日誌和redo日誌是透過兩階段提交來保持一致性的。否則如果資料庫系統發生crash,則透過redo日誌恢復的資料庫和透過binlog日誌恢復出來的臨時庫不一致。