MySQL存儲引擎InnoDB架構(gòu)原理和執(zhí)行流程
InnoDB 是 MySQL 的當(dāng)前默認(rèn)存儲引擎。該引擎支持外鍵、行級鎖定和 ACID 事務(wù)。這些功能使 InnoDB 成為現(xiàn)代應(yīng)用程序的可靠且合適的選擇。它的崩潰恢復(fù)機(jī)制、數(shù)據(jù)完整性和高性能是 InnoDB 目前成為默認(rèn) MySQL 引擎的一些原因。
更新語句在MySQL中是如何執(zhí)行的
假設(shè)有一條如下這樣的SQL語句,那么這條語句是如何執(zhí)行的呢?
update users set name = 'xxx' where id = 1;
首先Java系統(tǒng)會通過一個數(shù)據(jù)庫連接將該SQL語句發(fā)送到MySQL上,然后經(jīng)過SQL接口、查詢解析器、查詢優(yōu)化器、執(zhí)行器環(huán)節(jié),在解析出了SQL語句、生成了執(zhí)行計劃后,再由執(zhí)行器調(diào)用InnoDB存儲引擎的接口去執(zhí)行生成的執(zhí)行計劃。

下面介紹InnoDB存儲引擎里的架構(gòu)設(shè)計,以及如何基于InnoDB存儲引擎完成一條更新語句的執(zhí)行。
重要的內(nèi)存結(jié)構(gòu)—Buffer Pool緩沖池
InnoDB有一個非常重要、放在內(nèi)存里的組件,就是緩沖池(Buffer Pool)。緩沖池會緩存很多磁盤文件數(shù)據(jù),以便在查詢時不用去查磁盤。如下圖示:

所以當(dāng)InnoDB存儲引擎要執(zhí)行更新語句時:比如對"id=1"這一行數(shù)據(jù),會先判斷"id=1"這一行數(shù)據(jù)是否在緩沖池里。如果不在,則直接從磁盤里加載到緩沖池里,且對這行記錄加獨占鎖。
undo日志文件如何讓更新的數(shù)據(jù)可以回滾
假設(shè)"id=1"這行數(shù)據(jù)的name原來是"zhangsan",現(xiàn)在要更新為"xxx"。那么InnoDB得先把原值"zhangsan"和"id=1"寫入到undo日志文件中。
Java系統(tǒng)在執(zhí)行一條SQL更新語句時,要是它在一個事務(wù)里,那么事務(wù)提交前是可以對數(shù)據(jù)進(jìn)行回滾的。所以考慮到可能要回滾數(shù)據(jù),InnoDB會把更新前的值寫入undo日志文件。如下圖示:

更新Buffer Pool緩沖池中的緩存數(shù)據(jù)
當(dāng)InnoDB把要更新的那行記錄從磁盤文件加載到了緩沖池,同時對它加完鎖,而且還把更新前的舊值寫入undo日志文件后,InnoDB就可以正式開始更新這行記錄了。
更新的時候,會先更新緩沖池中的記錄,此時這個數(shù)據(jù)就是臟數(shù)據(jù)了。所謂的更新內(nèi)存緩沖池里的數(shù)據(jù),意思就是把內(nèi)存里的"id=1"這行數(shù)據(jù)的name字段修改為"xxx"。
為什么說此時這行數(shù)據(jù)是臟數(shù)據(jù)呢?因為這時磁盤上"id=1"這行數(shù)據(jù)的name字段還是"zhangsan",但內(nèi)存里這行數(shù)據(jù)已經(jīng)被修改了,所以它是臟數(shù)據(jù)。

Redo Log Buffer如何避免宕機(jī)時數(shù)據(jù)丟失
現(xiàn)在已經(jīng)把內(nèi)存里的數(shù)據(jù)進(jìn)行了修改,但是磁盤上的數(shù)據(jù)還沒修改。此時萬一MySQL所在機(jī)器宕機(jī),必然會導(dǎo)致內(nèi)存里已修改的數(shù)據(jù)丟失。這該如何處理?
為此必須把對內(nèi)存所做的修改寫入一個Redo Log Buffer里,Redo Log Buffer也是內(nèi)存里的一個緩沖區(qū),是用來存放redo日志的。
所謂redo日志,就是記錄InnoDB要對數(shù)據(jù)做什么修改。比如對"id=1"這行記錄修改name字段的值為"xxx",就是一條redo日志。

如果還沒提交事務(wù)時MySQL宕機(jī)了怎么辦
在數(shù)據(jù)庫中,哪怕執(zhí)行一條SQL語句,其實也可以是一個獨立的事務(wù)。只有當(dāng)事務(wù)提交后,SQL語句才算執(zhí)行結(jié)束。
所以如果還沒提交事務(wù)MySQL宕機(jī)了,那么必然導(dǎo)致內(nèi)存里Buffer Pool中修改過的數(shù)據(jù)都丟失,同時寫入Redo Log Buffer中的日志也會丟失。

此時數(shù)據(jù)丟失其實是不要緊的。因為一條更新語句只要沒提交事務(wù),那么就代表還沒執(zhí)行成功。此時MySQL宕機(jī)雖然導(dǎo)致內(nèi)存里的數(shù)據(jù)丟失,但還沒影響磁盤上的數(shù)據(jù)。
提交事務(wù)時將redo日志寫入磁盤中
如果InnoDB想要提交一個事務(wù),就會根據(jù)一定的策略把redo日志從Redo Log Buffer中刷入到磁盤文件里,這個策略是通過如下這個參數(shù)來配置的:innodb_flush_log_at_trx_commit。
(1)當(dāng)innodb_flush_log_at_trx_commit = 0時
那么進(jìn)行事務(wù)提交時,不會把Redo Log Buffer的數(shù)據(jù)刷入到磁盤文件里。這時即便提交了事務(wù),但如果MySQL宕機(jī)了,內(nèi)存里的數(shù)據(jù)也會全部丟失而且redo日志里沒有數(shù)據(jù)。

(2)當(dāng)innodb_flush_log_at_trx_commit = 1時
那么進(jìn)行事務(wù)提交時,會把內(nèi)存中的redo log刷入到磁盤文件里。只要事務(wù)提交成功,那么redo log就必然在磁盤里。哪怕此時Buffer Pool中更新過的數(shù)據(jù)還沒刷新到磁盤,系統(tǒng)崩潰重啟后,也可以根據(jù)磁盤中的redo log恢復(fù)。

(3)當(dāng)innodb_flush_log_at_trx_commit = 2時
那么進(jìn)行事務(wù)提交時,會把內(nèi)存中的redo log寫入到OS Cache緩存里。OS Cache緩存里的數(shù)據(jù)可能在1秒后才會被寫入到磁盤文件中。
在這種模式下,當(dāng)InnoDB存儲引擎提交事務(wù)后,redo log可能還停留在OS Cache緩存里,還沒實際進(jìn)入到磁盤文件。而此時MySQL所在機(jī)器宕機(jī)了,那么OS Cache里的redo log也會丟失。從而出現(xiàn)即便提交了事務(wù),但是數(shù)據(jù)還是丟失了的情況。

redo日志刷盤策略的選擇和建議
通常建議設(shè)置innodb_flush_log_at_trx_commit的值為1。也就是提交事務(wù)時,redo日志必須同時刷入磁盤文件里。這樣可以嚴(yán)格保證提交事務(wù)后數(shù)據(jù)絕對不會丟失。
如果innodb_flush_log_at_trx_commit = 0,那么提交事務(wù)后如果MySQL宕機(jī)而此時redo日志還沒有刷盤,則會導(dǎo)致內(nèi)存里的redo日志丟失,內(nèi)存更新好的數(shù)據(jù)也丟失。
如果innodb_flush_log_at_trx_commit = 2,那么提交事務(wù)后雖然redo日志進(jìn)入了OS Cache,但OS Cache的數(shù)據(jù)此時還沒進(jìn)入磁盤文件而MySQL機(jī)器宕機(jī)了,則也會導(dǎo)致OS Cache的redo日志丟失。
所以一般設(shè)置redo日志刷盤策略為1,保證事務(wù)提交后數(shù)據(jù)不會丟失。
MySQL的redo log和binlog對比
MySQL的redo log,是一種偏向物理性的重做日志。因為其記錄的是:對哪個數(shù)據(jù)頁中的哪條記錄做了什么修改。而且redo log是屬于InnoDB存儲引擎特有的日志文件。
MySQL的binlog,是一種偏向于邏輯性的日志,也叫歸檔日志。類似"對users表中id=1的一行記錄做了更新操作,更新后的值是什么"。binlog不是InnoDB存儲引擎特有的日志文件,binlog是屬于MySQL數(shù)據(jù)庫層面的日志文件。
提交事務(wù)時同時也會寫入binlog
提交事務(wù)時,除了會把redo日志寫入到磁盤文件中,還會把這次SQL更新對應(yīng)的binlog日志寫入到磁盤文件中。
下圖加入了執(zhí)行器這個組件,它會負(fù)責(zé)和InnoDB存儲引擎進(jìn)行交互:
步驟1:從磁盤加載數(shù)據(jù)到Buffer Pool緩存
步驟2:寫入undo日志
步驟3:更新Buffer Pool里的數(shù)據(jù)
步驟4:寫入redo日志到Redo Log Buffer
步驟5:redo日志刷入磁盤
步驟6:寫入binlog日志
實際上,執(zhí)行器是非常核心的一個組件。執(zhí)行器會與存儲引擎完成SQL語句在磁盤與內(nèi)存層面的全部數(shù)據(jù)更新操作。
下圖把一次更新語句的執(zhí)行,拆分為兩個階段。其中步驟1、2、3、4是執(zhí)行更新語句的階段,而步驟5和6是屬于提交事務(wù)的階段。

binlog日志的刷盤策略分析
binlog日志也有不同的刷盤策略,通過sync_binlog參數(shù)可以控制binlog的刷盤策略,默認(rèn)值是0。
(1)當(dāng)sync_binlog設(shè)置為0時
表示執(zhí)行器沒有直接將binlog寫入磁盤文件,而是先將binlog寫入OS Cache緩存,與redo log的innodb_flush_log_at_trx_commit的值為2一樣。
如果OS Cache里的數(shù)據(jù)還沒寫入磁盤文件時,MySQL所在機(jī)器宕機(jī),那么binlog日志也會丟失。

(2)當(dāng)sync_binlog設(shè)置為1時
表示在提交事務(wù)時,執(zhí)行器會把binlog直接寫入到磁盤文件中。這樣在提交事務(wù)后即便宕機(jī),binlog也不會丟失。

基于binlog的redo log完成事務(wù)的提交
當(dāng)MySQL把binlog寫入磁盤后,接著就會完成最終的事務(wù)提交。此時會把本次更新對應(yīng)的binlog文件名稱和位置,都寫入到redo日志里,同時在redo日志文件里寫入一個commit標(biāo)記。在完成這個事情后,才算是最終完成事務(wù)的提交。

在redo日志中寫入commit標(biāo)記的意義
寫入commit標(biāo)記是用來保持redo日志與binlog日志一致。也就是說,在提交事務(wù)的時候,上圖的步驟5、6、7必須都執(zhí)行完畢,才算是提交了事務(wù)。
(1)如果剛完成步驟5時,redo日志剛刷入到磁盤文件,MySQL宕機(jī)了
這時因為在redo日志沒有最終的事務(wù)commit標(biāo)記,所以此次事務(wù)不成功。因為不允許出現(xiàn)這樣的情況:redo日志文件里有更新日志,但是binlog日志文件里沒有對應(yīng)的更新日志。否則就會導(dǎo)致數(shù)據(jù)不一致。
(2)如果在完成步驟6時,binlog日志已寫入磁盤,MySQL宕機(jī)了
這時因為在redo日志沒有最終的事務(wù)commit標(biāo)記,所以此次事務(wù)也失敗。所以必須要在redo日志寫入最終的事務(wù)commit標(biāo)記,才算事務(wù)提交成功。這樣redo日志有本次更新的日志,binlog日志也有本次更新的日志,從而實現(xiàn)redo日志和binlog日志完全一致。
后臺IO線程隨機(jī)將內(nèi)存更新后的臟數(shù)據(jù)刷盤
當(dāng)完成事務(wù)提交后,MySQL已把內(nèi)存中的Buffer Pool緩存數(shù)據(jù)更新了,同時磁盤里也有redo日志和binlog日志,但磁盤上的數(shù)據(jù)文件還是舊值。
這時MySQL會有一個后臺IO線程,在事務(wù)提交后的某個時間,隨機(jī)把內(nèi)存Buffer Pool中修改后的臟數(shù)據(jù)刷回到磁盤上的數(shù)據(jù)文件里。

當(dāng)IO線程把Buffer Pool里修改后的臟數(shù)據(jù)刷回磁盤后,磁盤上的數(shù)據(jù)才會跟內(nèi)存里的數(shù)據(jù)一樣,都是修改后的值。
當(dāng)IO線程把臟數(shù)據(jù)刷回磁盤之前,即便MySQL宕機(jī)也沒關(guān)系。因為重啟后會根據(jù)redo日志恢復(fù)提交事務(wù)時所做的修改到內(nèi)存里。之后IO線程還是會把修改后的數(shù)據(jù)刷到磁盤的數(shù)據(jù)文件里。
InnoDB存儲引擎的架構(gòu)原理總結(jié)
InnoDB存儲引擎會使用Buffer Pool、Redo Log Buffer來緩存數(shù)據(jù)。InnoDB存儲引擎有屬于自己的undo日志文件、redo日志文件,MySQL也有屬于自己的binlog日志文件。
執(zhí)行更新時:會修改Buffer Pool里的數(shù)據(jù)、寫undo日志、寫Redo Log Buffer。
提交事務(wù)時:會把binlog刷入磁盤、在redo日志中寫入事務(wù)標(biāo)記,把redo日志刷入磁盤。最后InnoDB后臺的IO線程會隨機(jī)把Buffer Pool的臟數(shù)據(jù)刷入到磁盤文件。
(1)MySQL宕機(jī)重啟如何確定是否需要從redo日志恢復(fù)數(shù)據(jù)
MySQL宕機(jī)重啟,如何確定臟數(shù)據(jù)在宕機(jī)前是否已全部刷寫回磁盤文件。
MySQL宕機(jī)重啟,InnoDB會首先去查看數(shù)據(jù)頁中LSN的數(shù)值。LSN就是InnoDB使用的一個版本標(biāo)記的計數(shù)。如果數(shù)據(jù)頁中的LSN異于redo日志的commit標(biāo)記,那么就去查看redo日志的LSN大小。如果數(shù)據(jù)頁的LSN值大,則說明數(shù)據(jù)頁領(lǐng)先redo日志,不需要恢復(fù),反之則需要從redo日志中恢復(fù)。
(2)從redo日志恢復(fù)數(shù)據(jù)時是全量恢復(fù)還是指定位置后恢復(fù)
redo日志是劃歸于一個redo日志組的。默認(rèn)一個redo日志組有兩個redo日志文件。寫redo日志時是循環(huán)寫入,寫滿一個redo日志文件再寫另外一個。
在寫滿切換redo日志文件時,會觸發(fā)數(shù)據(jù)庫的檢查點checkpoint。checkpoint所做的事就是把臟頁刷新回磁盤。
當(dāng)DB重啟恢復(fù)時只需要恢復(fù)checkpoint之后的數(shù)據(jù)即可。所以redo日志文件大小不宜過大,不然導(dǎo)致恢復(fù)時需要更長的時間。redo日志文件大小也不宜過小,不然導(dǎo)致頻繁切換觸發(fā)檢測點降低性能。
(3)既然有redo日志來保證崩潰恢復(fù),為什么還要有binlog日志
binlog日志其實就是歸檔日志,主要用來做數(shù)據(jù)恢復(fù)的。MySQL最開始設(shè)計時只有MyISAM引擎只有binlog,不支持InnoDB。此外數(shù)據(jù)庫備份以及hadoop系統(tǒng)數(shù)據(jù)分析都是binlog來實現(xiàn)的,所以還需要binlog。
(4)redo日志和binlog日志的數(shù)據(jù)結(jié)構(gòu)是怎樣的
redo日志是循環(huán)寫,會把redo日志分為0,1,2,3四個區(qū)間,有兩個指針。writepos指針是一邊寫一邊向后移動,checkpoint指針是一邊擦除一邊向后移動。所以redo日志是不能保存很多記錄的,必須持久化到磁盤中。binlog日志是追加寫,不會覆蓋之前的日志。
(5)binlog日志和redo日志是怎么保持一致性的
binlog日志和redo日志是通過兩階段提交來保持一致性的。否則如果數(shù)據(jù)庫系統(tǒng)發(fā)生crash,則通過redo日志恢復(fù)的數(shù)據(jù)庫和通過binlog日志恢復(fù)出來的臨時庫不一致。
總結(jié)
到此這篇關(guān)于MySQL存儲引擎InnoDB架構(gòu)原理和執(zhí)行流程的文章就介紹到這了,更多相關(guān)MySQL中InnoDB架構(gòu)原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL獲取版本號的N種方法(全網(wǎng)最全以及避坑大全)
相信大家在工作學(xué)習(xí)中經(jīng)常有需要查詢 MySQL 數(shù)據(jù)庫版本的情況,這里給大家介紹幾種在 MySQL 中查詢數(shù)據(jù)庫版本的方法以及每種方法適用的場景,相信總有一種可以幫到你,需要的朋友可以參考下2024-10-10
MySQL主機(jī)因多次連接數(shù)據(jù)庫錯誤而被阻塞的解決方案
文章講述了MySQL數(shù)據(jù)庫連接失敗導(dǎo)致主機(jī)被阻塞的問題,分析了原因,并提供了兩種解決方法:調(diào)整max_connect_errors參數(shù)和執(zhí)行FLUSH HOSTS命令2025-12-12
MySQL數(shù)據(jù)庫重命名的快速且安全方法(3種)
這篇文章主要介紹了MySQL數(shù)據(jù)庫重命名的快速且安全方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
MySQL錯誤Forcing close of thread的兩種解決方法
這篇文章主要介紹了MySQL錯誤Forcing close of thread的兩種解決方法,需要的朋友可以參考下2014-11-11
Apache Drill 連接 MySQL 或 PostgreSQL&
Drill 連接 MySQL或PostgreSQL 數(shù)據(jù)庫需要通過配置JDBC存儲插件實現(xiàn),核心是通過JDBC 驅(qū)動連接外部數(shù)據(jù)庫,下面給大家介紹Apache Drill 連接 MySQL 或 PostgreSQL 數(shù)據(jù)庫的詳細(xì)步驟,感興趣的朋友跟隨小編一起看看吧2025-11-11
使用MySQL Workbench構(gòu)建ER圖的詳細(xì)教程
ER圖又稱實體-聯(lián)系圖(Entity Relationship Diagram),提供了表示實體類型、屬性和聯(lián)系的方法,用來描述現(xiàn)實世界的概念模型,MySQL?Workbench是一個強(qiáng)大的數(shù)據(jù)庫設(shè)計工具,提供了便捷的數(shù)據(jù)導(dǎo)入導(dǎo)出功能,本文介紹了使用MySQL Workbench構(gòu)建ER圖的詳細(xì)教程2024-06-06

