MySQL存儲(chǔ)引擎InnoDB架構(gòu)原理和執(zhí)行流程
InnoDB 是 MySQL 的當(dāng)前默認(rèn)存儲(chǔ)引擎。該引擎支持外鍵、行級(jí)鎖定和 ACID 事務(wù)。這些功能使 InnoDB 成為現(xiàn)代應(yīng)用程序的可靠且合適的選擇。它的崩潰恢復(fù)機(jī)制、數(shù)據(jù)完整性和高性能是 InnoDB 目前成為默認(rèn) MySQL 引擎的一些原因。
更新語(yǔ)句在MySQL中是如何執(zhí)行的
假設(shè)有一條如下這樣的SQL語(yǔ)句,那么這條語(yǔ)句是如何執(zhí)行的呢?
update users set name = 'xxx' where id = 1;
首先Java系統(tǒng)會(huì)通過一個(gè)數(shù)據(jù)庫(kù)連接將該SQL語(yǔ)句發(fā)送到MySQL上,然后經(jīng)過SQL接口、查詢解析器、查詢優(yōu)化器、執(zhí)行器環(huán)節(jié),在解析出了SQL語(yǔ)句、生成了執(zhí)行計(jì)劃后,再由執(zhí)行器調(diào)用InnoDB存儲(chǔ)引擎的接口去執(zhí)行生成的執(zhí)行計(jì)劃。
下面介紹InnoDB存儲(chǔ)引擎里的架構(gòu)設(shè)計(jì),以及如何基于InnoDB存儲(chǔ)引擎完成一條更新語(yǔ)句的執(zhí)行。
重要的內(nèi)存結(jié)構(gòu)—Buffer Pool緩沖池
InnoDB有一個(gè)非常重要、放在內(nèi)存里的組件,就是緩沖池(Buffer Pool)。緩沖池會(huì)緩存很多磁盤文件數(shù)據(jù),以便在查詢時(shí)不用去查磁盤。如下圖示:
所以當(dāng)InnoDB存儲(chǔ)引擎要執(zhí)行更新語(yǔ)句時(shí):比如對(duì)"id=1"這一行數(shù)據(jù),會(huì)先判斷"id=1"這一行數(shù)據(jù)是否在緩沖池里。如果不在,則直接從磁盤里加載到緩沖池里,且對(duì)這行記錄加獨(dú)占鎖。
undo日志文件如何讓更新的數(shù)據(jù)可以回滾
假設(shè)"id=1"這行數(shù)據(jù)的name原來是"zhangsan",現(xiàn)在要更新為"xxx"。那么InnoDB得先把原值"zhangsan"和"id=1"寫入到undo日志文件中。
Java系統(tǒng)在執(zhí)行一條SQL更新語(yǔ)句時(shí),要是它在一個(gè)事務(wù)里,那么事務(wù)提交前是可以對(duì)數(shù)據(jù)進(jìn)行回滾的。所以考慮到可能要回滾數(shù)據(jù),InnoDB會(huì)把更新前的值寫入undo日志文件。如下圖示:
更新Buffer Pool緩沖池中的緩存數(shù)據(jù)
當(dāng)InnoDB把要更新的那行記錄從磁盤文件加載到了緩沖池,同時(shí)對(duì)它加完鎖,而且還把更新前的舊值寫入undo日志文件后,InnoDB就可以正式開始更新這行記錄了。
更新的時(shí)候,會(huì)先更新緩沖池中的記錄,此時(shí)這個(gè)數(shù)據(jù)就是臟數(shù)據(jù)了。所謂的更新內(nèi)存緩沖池里的數(shù)據(jù),意思就是把內(nèi)存里的"id=1"這行數(shù)據(jù)的name字段修改為"xxx"。
為什么說此時(shí)這行數(shù)據(jù)是臟數(shù)據(jù)呢?因?yàn)檫@時(shí)磁盤上"id=1"這行數(shù)據(jù)的name字段還是"zhangsan",但內(nèi)存里這行數(shù)據(jù)已經(jīng)被修改了,所以它是臟數(shù)據(jù)。
Redo Log Buffer如何避免宕機(jī)時(shí)數(shù)據(jù)丟失
現(xiàn)在已經(jīng)把內(nèi)存里的數(shù)據(jù)進(jìn)行了修改,但是磁盤上的數(shù)據(jù)還沒修改。此時(shí)萬(wàn)一MySQL所在機(jī)器宕機(jī),必然會(huì)導(dǎo)致內(nèi)存里已修改的數(shù)據(jù)丟失。這該如何處理?
為此必須把對(duì)內(nèi)存所做的修改寫入一個(gè)Redo Log Buffer里,Redo Log Buffer也是內(nèi)存里的一個(gè)緩沖區(qū),是用來存放redo日志的。
所謂redo日志,就是記錄InnoDB要對(duì)數(shù)據(jù)做什么修改。比如對(duì)"id=1"這行記錄修改name字段的值為"xxx",就是一條redo日志。
如果還沒提交事務(wù)時(shí)MySQL宕機(jī)了怎么辦
在數(shù)據(jù)庫(kù)中,哪怕執(zhí)行一條SQL語(yǔ)句,其實(shí)也可以是一個(gè)獨(dú)立的事務(wù)。只有當(dāng)事務(wù)提交后,SQL語(yǔ)句才算執(zhí)行結(jié)束。
所以如果還沒提交事務(wù)MySQL宕機(jī)了,那么必然導(dǎo)致內(nèi)存里Buffer Pool中修改過的數(shù)據(jù)都丟失,同時(shí)寫入Redo Log Buffer中的日志也會(huì)丟失。
此時(shí)數(shù)據(jù)丟失其實(shí)是不要緊的。因?yàn)橐粭l更新語(yǔ)句只要沒提交事務(wù),那么就代表還沒執(zhí)行成功。此時(shí)MySQL宕機(jī)雖然導(dǎo)致內(nèi)存里的數(shù)據(jù)丟失,但還沒影響磁盤上的數(shù)據(jù)。
提交事務(wù)時(shí)將redo日志寫入磁盤中
如果InnoDB想要提交一個(gè)事務(wù),就會(huì)根據(jù)一定的策略把redo日志從Redo Log Buffer中刷入到磁盤文件里,這個(gè)策略是通過如下這個(gè)參數(shù)來配置的:innodb_flush_log_at_trx_commit。
(1)當(dāng)innodb_flush_log_at_trx_commit = 0時(shí)
那么進(jìn)行事務(wù)提交時(shí),不會(huì)把Redo Log Buffer的數(shù)據(jù)刷入到磁盤文件里。這時(shí)即便提交了事務(wù),但如果MySQL宕機(jī)了,內(nèi)存里的數(shù)據(jù)也會(huì)全部丟失而且redo日志里沒有數(shù)據(jù)。
(2)當(dāng)innodb_flush_log_at_trx_commit = 1時(shí)
那么進(jìn)行事務(wù)提交時(shí),會(huì)把內(nèi)存中的redo log刷入到磁盤文件里。只要事務(wù)提交成功,那么redo log就必然在磁盤里。哪怕此時(shí)Buffer Pool中更新過的數(shù)據(jù)還沒刷新到磁盤,系統(tǒng)崩潰重啟后,也可以根據(jù)磁盤中的redo log恢復(fù)。
(3)當(dāng)innodb_flush_log_at_trx_commit = 2時(shí)
那么進(jìn)行事務(wù)提交時(shí),會(huì)把內(nèi)存中的redo log寫入到OS Cache緩存里。OS Cache緩存里的數(shù)據(jù)可能在1秒后才會(huì)被寫入到磁盤文件中。
在這種模式下,當(dāng)InnoDB存儲(chǔ)引擎提交事務(wù)后,redo log可能還停留在OS Cache緩存里,還沒實(shí)際進(jìn)入到磁盤文件。而此時(shí)MySQL所在機(jī)器宕機(jī)了,那么OS Cache里的redo log也會(huì)丟失。從而出現(xiàn)即便提交了事務(wù),但是數(shù)據(jù)還是丟失了的情況。
redo日志刷盤策略的選擇和建議
通常建議設(shè)置innodb_flush_log_at_trx_commit的值為1。也就是提交事務(wù)時(shí),redo日志必須同時(shí)刷入磁盤文件里。這樣可以嚴(yán)格保證提交事務(wù)后數(shù)據(jù)絕對(duì)不會(huì)丟失。
如果innodb_flush_log_at_trx_commit = 0,那么提交事務(wù)后如果MySQL宕機(jī)而此時(shí)redo日志還沒有刷盤,則會(huì)導(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ù)此時(shí)還沒進(jìn)入磁盤文件而MySQL機(jī)器宕機(jī)了,則也會(huì)導(dǎo)致OS Cache的redo日志丟失。
所以一般設(shè)置redo日志刷盤策略為1,保證事務(wù)提交后數(shù)據(jù)不會(huì)丟失。
MySQL的redo log和binlog對(duì)比
MySQL的redo log,是一種偏向物理性的重做日志。因?yàn)槠溆涗浀氖牵簩?duì)哪個(gè)數(shù)據(jù)頁(yè)中的哪條記錄做了什么修改。而且redo log是屬于InnoDB存儲(chǔ)引擎特有的日志文件。
MySQL的binlog,是一種偏向于邏輯性的日志,也叫歸檔日志。類似"對(duì)users表中id=1的一行記錄做了更新操作,更新后的值是什么"。binlog不是InnoDB存儲(chǔ)引擎特有的日志文件,binlog是屬于MySQL數(shù)據(jù)庫(kù)層面的日志文件。
提交事務(wù)時(shí)同時(shí)也會(huì)寫入binlog
提交事務(wù)時(shí),除了會(huì)把redo日志寫入到磁盤文件中,還會(huì)把這次SQL更新對(duì)應(yīng)的binlog日志寫入到磁盤文件中。
下圖加入了執(zhí)行器這個(gè)組件,它會(huì)負(fù)責(zé)和InnoDB存儲(chǔ)引擎進(jìn)行交互:
步驟1:從磁盤加載數(shù)據(jù)到Buffer Pool緩存
步驟2:寫入undo日志
步驟3:更新Buffer Pool里的數(shù)據(jù)
步驟4:寫入redo日志到Redo Log Buffer
步驟5:redo日志刷入磁盤
步驟6:寫入binlog日志
實(shí)際上,執(zhí)行器是非常核心的一個(gè)組件。執(zhí)行器會(huì)與存儲(chǔ)引擎完成SQL語(yǔ)句在磁盤與內(nèi)存層面的全部數(shù)據(jù)更新操作。
下圖把一次更新語(yǔ)句的執(zhí)行,拆分為兩個(gè)階段。其中步驟1、2、3、4是執(zhí)行更新語(yǔ)句的階段,而步驟5和6是屬于提交事務(wù)的階段。
binlog日志的刷盤策略分析
binlog日志也有不同的刷盤策略,通過sync_binlog參數(shù)可以控制binlog的刷盤策略,默認(rèn)值是0。
(1)當(dāng)sync_binlog設(shè)置為0時(shí)
表示執(zhí)行器沒有直接將binlog寫入磁盤文件,而是先將binlog寫入OS Cache緩存,與redo log的innodb_flush_log_at_trx_commit的值為2一樣。
如果OS Cache里的數(shù)據(jù)還沒寫入磁盤文件時(shí),MySQL所在機(jī)器宕機(jī),那么binlog日志也會(huì)丟失。
(2)當(dāng)sync_binlog設(shè)置為1時(shí)
表示在提交事務(wù)時(shí),執(zhí)行器會(huì)把binlog直接寫入到磁盤文件中。這樣在提交事務(wù)后即便宕機(jī),binlog也不會(huì)丟失。
基于binlog的redo log完成事務(wù)的提交
當(dāng)MySQL把binlog寫入磁盤后,接著就會(huì)完成最終的事務(wù)提交。此時(shí)會(huì)把本次更新對(duì)應(yīng)的binlog文件名稱和位置,都寫入到redo日志里,同時(shí)在redo日志文件里寫入一個(gè)commit標(biāo)記。在完成這個(gè)事情后,才算是最終完成事務(wù)的提交。
在redo日志中寫入commit標(biāo)記的意義
寫入commit標(biāo)記是用來保持redo日志與binlog日志一致。也就是說,在提交事務(wù)的時(shí)候,上圖的步驟5、6、7必須都執(zhí)行完畢,才算是提交了事務(wù)。
(1)如果剛完成步驟5時(shí),redo日志剛刷入到磁盤文件,MySQL宕機(jī)了
這時(shí)因?yàn)樵趓edo日志沒有最終的事務(wù)commit標(biāo)記,所以此次事務(wù)不成功。因?yàn)椴辉试S出現(xiàn)這樣的情況:redo日志文件里有更新日志,但是binlog日志文件里沒有對(duì)應(yīng)的更新日志。否則就會(huì)導(dǎo)致數(shù)據(jù)不一致。
(2)如果在完成步驟6時(shí),binlog日志已寫入磁盤,MySQL宕機(jī)了
這時(shí)因?yàn)樵趓edo日志沒有最終的事務(wù)commit標(biāo)記,所以此次事務(wù)也失敗。所以必須要在redo日志寫入最終的事務(wù)commit標(biāo)記,才算事務(wù)提交成功。這樣redo日志有本次更新的日志,binlog日志也有本次更新的日志,從而實(shí)現(xiàn)redo日志和binlog日志完全一致。
后臺(tái)IO線程隨機(jī)將內(nèi)存更新后的臟數(shù)據(jù)刷盤
當(dāng)完成事務(wù)提交后,MySQL已把內(nèi)存中的Buffer Pool緩存數(shù)據(jù)更新了,同時(shí)磁盤里也有redo日志和binlog日志,但磁盤上的數(shù)據(jù)文件還是舊值。
這時(shí)MySQL會(huì)有一個(gè)后臺(tái)IO線程,在事務(wù)提交后的某個(gè)時(shí)間,隨機(jī)把內(nèi)存Buffer Pool中修改后的臟數(shù)據(jù)刷回到磁盤上的數(shù)據(jù)文件里。
當(dāng)IO線程把Buffer Pool里修改后的臟數(shù)據(jù)刷回磁盤后,磁盤上的數(shù)據(jù)才會(huì)跟內(nèi)存里的數(shù)據(jù)一樣,都是修改后的值。
當(dāng)IO線程把臟數(shù)據(jù)刷回磁盤之前,即便MySQL宕機(jī)也沒關(guān)系。因?yàn)橹貑⒑髸?huì)根據(jù)redo日志恢復(fù)提交事務(wù)時(shí)所做的修改到內(nèi)存里。之后IO線程還是會(huì)把修改后的數(shù)據(jù)刷到磁盤的數(shù)據(jù)文件里。
InnoDB存儲(chǔ)引擎的架構(gòu)原理總結(jié)
InnoDB存儲(chǔ)引擎會(huì)使用Buffer Pool、Redo Log Buffer來緩存數(shù)據(jù)。InnoDB存儲(chǔ)引擎有屬于自己的undo日志文件、redo日志文件,MySQL也有屬于自己的binlog日志文件。
執(zhí)行更新時(shí):會(huì)修改Buffer Pool里的數(shù)據(jù)、寫undo日志、寫Redo Log Buffer。
提交事務(wù)時(shí):會(huì)把binlog刷入磁盤、在redo日志中寫入事務(wù)標(biāo)記,把redo日志刷入磁盤。最后InnoDB后臺(tái)的IO線程會(huì)隨機(jī)把Buffer Pool的臟數(shù)據(jù)刷入到磁盤文件。
(1)MySQL宕機(jī)重啟如何確定是否需要從redo日志恢復(fù)數(shù)據(jù)
MySQL宕機(jī)重啟,如何確定臟數(shù)據(jù)在宕機(jī)前是否已全部刷寫回磁盤文件。
MySQL宕機(jī)重啟,InnoDB會(huì)首先去查看數(shù)據(jù)頁(yè)中LSN的數(shù)值。LSN就是InnoDB使用的一個(gè)版本標(biāo)記的計(jì)數(shù)。如果數(shù)據(jù)頁(yè)中的LSN異于redo日志的commit標(biāo)記,那么就去查看redo日志的LSN大小。如果數(shù)據(jù)頁(yè)的LSN值大,則說明數(shù)據(jù)頁(yè)領(lǐng)先redo日志,不需要恢復(fù),反之則需要從redo日志中恢復(fù)。
(2)從redo日志恢復(fù)數(shù)據(jù)時(shí)是全量恢復(fù)還是指定位置后恢復(fù)
redo日志是劃歸于一個(gè)redo日志組的。默認(rèn)一個(gè)redo日志組有兩個(gè)redo日志文件。寫redo日志時(shí)是循環(huán)寫入,寫滿一個(gè)redo日志文件再寫另外一個(gè)。
在寫滿切換redo日志文件時(shí),會(huì)觸發(fā)數(shù)據(jù)庫(kù)的檢查點(diǎn)checkpoint。checkpoint所做的事就是把臟頁(yè)刷新回磁盤。
當(dāng)DB重啟恢復(fù)時(shí)只需要恢復(fù)checkpoint之后的數(shù)據(jù)即可。所以redo日志文件大小不宜過大,不然導(dǎo)致恢復(fù)時(shí)需要更長(zhǎng)的時(shí)間。redo日志文件大小也不宜過小,不然導(dǎo)致頻繁切換觸發(fā)檢測(cè)點(diǎn)降低性能。
(3)既然有redo日志來保證崩潰恢復(fù),為什么還要有binlog日志
binlog日志其實(shí)就是歸檔日志,主要用來做數(shù)據(jù)恢復(fù)的。MySQL最開始設(shè)計(jì)時(shí)只有MyISAM引擎只有binlog,不支持InnoDB。此外數(shù)據(jù)庫(kù)備份以及hadoop系統(tǒng)數(shù)據(jù)分析都是binlog來實(shí)現(xiàn)的,所以還需要binlog。
(4)redo日志和binlog日志的數(shù)據(jù)結(jié)構(gòu)是怎樣的
redo日志是循環(huán)寫,會(huì)把redo日志分為0,1,2,3四個(gè)區(qū)間,有兩個(gè)指針。writepos指針是一邊寫一邊向后移動(dòng),checkpoint指針是一邊擦除一邊向后移動(dòng)。所以redo日志是不能保存很多記錄的,必須持久化到磁盤中。binlog日志是追加寫,不會(huì)覆蓋之前的日志。
(5)binlog日志和redo日志是怎么保持一致性的
binlog日志和redo日志是通過兩階段提交來保持一致性的。否則如果數(shù)據(jù)庫(kù)系統(tǒng)發(fā)生crash,則通過redo日志恢復(fù)的數(shù)據(jù)庫(kù)和通過binlog日志恢復(fù)出來的臨時(shí)庫(kù)不一致。
總結(jié)
到此這篇關(guān)于MySQL存儲(chǔ)引擎InnoDB架構(gòu)原理和執(zhí)行流程的文章就介紹到這了,更多相關(guān)MySQL中InnoDB架構(gòu)原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SQL Server 數(shù)據(jù)庫(kù)的備份詳細(xì)介紹及注意事項(xiàng)
這篇文章主要介紹了SQL Server 備份詳細(xì)介紹及注意事項(xiàng)的相關(guān)資料,需要的朋友可以參考下2016-12-12mysql中text,longtext,mediumtext區(qū)別小結(jié)
在 MySQL 中,text、mediumtext 和 longtext 都是用來存儲(chǔ)大量文本數(shù)據(jù)的數(shù)據(jù)類型,本文就來詳細(xì)的介紹一下這三種類型的區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12PHP mysqli擴(kuò)展庫(kù) 預(yù)處理技術(shù)的使用分析
本篇文章,介紹了PHP mysqli擴(kuò)展庫(kù) 預(yù)處理技術(shù)的使用分析。需要的朋友參考下2013-05-05Mysql內(nèi)置函數(shù)的實(shí)現(xiàn)示例
mysql內(nèi)置了很多的函數(shù),本文主要介紹了Mysql內(nèi)置函數(shù)的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07在WIN命令提示符下mysql 用戶新建、授權(quán)、刪除,密碼修改
一般情況下,修改MySQL密碼,授權(quán),是需要有mysql里的root權(quán)限的,本操作是在WIN命令提示符下,感興趣的朋友可以參考下2013-11-11MySql 5.6.14 Win32位免安裝解壓縮版配置教程
本文給大家介紹mysql 5.6.14 win32 位免安裝解壓縮版配置方法,本文分步驟給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,對(duì)mysql5.6.14 免安裝解壓縮版配置方法感興趣的朋友一起看看吧2016-11-11MySQL查詢表中某列字段相同的重復(fù)數(shù)據(jù)的方法
在數(shù)據(jù)庫(kù)查詢中,我們經(jīng)常需要查找表中某列中重復(fù)的數(shù)據(jù),本文將介紹如何使用 SQL 查詢語(yǔ)句來查找表中某列字段相同的重復(fù)數(shù)據(jù),幫助你快速定位重復(fù)數(shù)據(jù)問題并進(jìn)行處理2023-08-08