通過一條語句的執行,深入理解InnoDB的底層架構
MySQL最常用的存儲引擎是innodb,我們今天就借助一條更新語句的執行,了解下innodb具體是如何處理的,深入理解下它的架構。
假設更新語句是這樣的:
- update user set name ='xxx' where id = 1;
這條SQL語句發送到MySQL上后,會經過SQL接口、解析器、優化器、執行器幾個階段,解析SQL、生成執行計劃,再由執行器調用存儲引擎執行這個執行計劃。
如下圖所示:
圖1 MySQL底層架構
下面我們就跟隨一條update語句,分析下innodb存儲引擎的架構設計。
1、innodb最重要的組件:緩沖池(BufferPool)
innodb存儲引擎中有一個非常重要的組件,就是緩沖池(BufferPool),這里面會緩沖很多數據,以便于以后操作數據的時候,可以直接操作內存,就不用訪問磁盤了。
圖2 innoDB重要組件緩沖池
innoDB執行上面那條更新語句的時候,會先看id = 1的這條語句是否在緩沖池中,如果不再就需要從磁盤加載到緩沖池來,而且還會對這條記錄加獨占鎖。
鎖相關的知識點,后面會有講解,這里不是重點,就不展開了。
2、undo日志文件
接下來,準備更新id = 1的這條數據時,會先把id = 1和name原來的值寫入到undo日志文件中去。
這么做的目的是什么?當然是方便回滾了。
MySQL增刪改數據都是放在事務里執行的,如果事務提交失敗了,就可以根據undo日志進行回滾。
圖3 undo日志文件
把id = 1的那條要更新的數據加載到緩沖池,把要更新數據的舊值寫入undo日志文件后,就可以開始更新這條記錄了。
更新的時候,先更新緩沖池的數據。更新完后,緩沖池里的數據就變成:name = 'xxx'了,而此時磁盤上的數據還是name='zhangsan'。此時innoDB數據狀態就變成這樣了:
圖4 更新緩沖池數據
3、redo日志文件
此時緩沖池和磁盤上的數據是不一致的,如果MySQL宕機了,怎么辦?
此時MySQL宕機了,緩沖池里的數據肯定就丟失了。
這時候,就要引入一個新的組件:redo日志。
redo日志也是一個內存緩沖區,用來存放redo日志的,就是用來記錄你對數據做了那些修改。
比如,id = 1這條記錄,修改了name,redo日志可能就這樣:id = 1, name = 'xxx'。
圖5 redo日志
有了redo log,MySQL宕機后重啟,就可以恢復更新后的數據。
但是,如果此時MySQL數據庫宕機了,會怎樣?
必然是緩沖池中修改過的數據,redo log buffer日志都會丟失。
但是,這也不要緊,因為你更新數據的事務沒有提交,此時MySQL宕機了,事務就執行失敗了,客戶端會收到一個數據庫異常,MySQL重啟后磁盤上的數據還是原樣子。
所以數據還是一致的。
另外,redo日志是innoDB特有的一個組件。
4、提交事務
上面的步驟完成之后,就要提交事務了,此時會把redo日志刷到磁盤上去。
刷盤策略可以通過innodb_flush_log_at_trx_commit來配置。
這個配置有幾個選項:
0,提交事務的時候,不會把redo日志刷入磁盤;
1,默認值,提交事務的時候,會把redo刷入磁盤,只要事務提交成功,redo日志就比如進入磁盤了。
2,提交事務的時候,會把redo刷入os cache。操作系統會不定期把os cache里的數據刷到磁盤里去。
所以innodb_flush_log_at_trx_commit等于0或2的時候,redo日志都有事務提交成功,沒寫進磁盤的可能,緩沖池里更新后的數據也丟失了。此時MySQL重啟,就無法根據redo恢復更新后的數據,就會出現數據不一致情況。
所以一般情況下,我們都會把innodb_flush_log_at_trx_commit配置為1。
圖6 redo日志
5、binlog日志
其實MySQL中提交事務的時候,還會記錄binlog。binlog是MySQL server自己的日志文件。
redo日志屬于一種偏向于物理性質的重做日志,它里面記錄的相當于是“對某某數據頁的某某記錄,做了某某修改”。
binlog叫做歸檔日志,它里面記錄的是偏向于邏輯性的日志,類似于redis的aof日志。
我們提交事物的時候,除了把redo log日志寫到磁盤,還會同時把對應的binlog日志寫到磁盤文件中。
圖7 binlog日志
與redo log日志一樣,binlog日志有兩種刷盤策略,相應的配置項為:sync_binlog。
0,默認值,提交事務的時候,會把binlog刷入os cache。
1,提交事務的時候,會把binlog寫入磁盤。
所以,當sync_binlog設置為0的時候,如果機器宕機,binlog會有丟失的風險。設置為1的時候,即使機器宕機,binlog日志也不會丟失。
當我們把binlog日志寫入磁盤后,接著就完成了最終的事務提交,最后會把本次更新對應的binlog日志文件名和這次更新的binlog日志在文件里的位置,都寫入到redo log日志里去,同時在redo log日志文件里寫入一個commit標記。
到此為止,一個事務提交才是完成了。
圖8 binlog刷到磁盤
最后再補充一點,在redo日志中寫入commit標識,其目的是保持redo log日志與binlog日志一致的。
也就是說,innoDB根據commit標識判定一個事務是否執行成功。如果在圖8的5、6、7步,必須是三個步驟都執行成功了,才算提交了事務。假如執行其中某個步驟的時候,機器宕機了,會怎樣?
這時候,因為redo日志里沒有commit標識,所以會判定此次事務執行不成功,就不會出現數據不一致的情況。
6、后臺線程把內存數據刷到磁盤
此時事務提交了,已經把緩沖池(BufferPool)中的數據更新了,磁盤里也有了redo日志和binlog日志,但這時候,磁盤上的數據還是舊的啊。
所以MySQL會有一個后臺IO線程,會在某個時間,隨機把緩沖池(BufferPool)中的數據刷到磁盤上去。
圖9 innoDB執行更新語句時的完整流程
后臺IO線程把緩沖池的數據刷到磁盤前,即使MySQL宕機,也沒關系,因為機器重啟后,會根據redo日志回復之前提交事務所作的修改。
7、總結
通過一次更新數據的流程,了解了innoDB存儲引擎做了哪些工作。更新前記錄undo日志,更新緩沖池(BufferPool)里的數據,記錄redo log日志,binlog日志,每一步都有其專門的作用,innoDB通過這套復雜的架構設計,保證了數據更新的高性能和一致性。