前言
在MySQL中InnoDB屬于存儲(chǔ)引擎層,并以插件的形式集成在數(shù)據(jù)庫(kù)中。從MySQL5.5.8開(kāi)始,InnoDB成為其默認(rèn)的存儲(chǔ)引擎。InnoDB存儲(chǔ)引擎支持事務(wù)、其設(shè)計(jì)目標(biāo)主要是面向OLTP的應(yīng)用,主要特點(diǎn)有:支持事務(wù)、行鎖設(shè)計(jì)支持高并發(fā)、外鍵支持、自動(dòng)崩潰恢復(fù)、聚簇索引的方式組織表結(jié)構(gòu)等。
體系架構(gòu)
InnoDB存儲(chǔ)引擎是由內(nèi)存池、后臺(tái)線程、磁盤(pán)存儲(chǔ)三大部分組成。
線程
InnoDB 使用的是多線程模型, 其后臺(tái)有多個(gè)不同的線程負(fù)責(zé)處理不同的任務(wù)
Master Thread
Master Thread是最核心的一個(gè)后臺(tái)線程,主要負(fù)責(zé)將緩沖池中的數(shù)據(jù)異步刷新到磁盤(pán),保證數(shù)據(jù)的一致性。包括臟頁(yè)刷新、合并插入緩沖、UNDO頁(yè)的回收等。
IO Thread
在 InnoDB 存儲(chǔ)引擎中大量使用了異步IO(Async IO)來(lái)處理寫(xiě)IO請(qǐng)求, IO Thread的工作主要是負(fù)責(zé)這些 IO 請(qǐng)求的回調(diào)。
Purge Thread
事務(wù)提交后,其所使用的undo log可能不再需要,因此需要Purge Thread來(lái)回收已經(jīng)分配并使用的UNDO頁(yè)。InnoDB支持多個(gè)Purge Thread, 這樣做可以加快UNDO頁(yè)的回收,提高CPU的使用率以及提升存儲(chǔ)引擎的性能。
Page Cleaner Thread
Page Cleaner Thread的作用是取代Master Thread中臟頁(yè)刷新的操作,其目的是減輕原Master Thread的工作及對(duì)于用戶查詢線程的阻塞,進(jìn)一步提高InnoDB存儲(chǔ)引擎的性能。
內(nèi)存
InnoDB 存儲(chǔ)引擎內(nèi)存的結(jié)構(gòu)
緩沖池
InnoDB存儲(chǔ)引擎是基于磁盤(pán)存儲(chǔ)的,并將其中的記錄按照頁(yè)的方式進(jìn)行管理。但是由于CPU速度和磁盤(pán)速度之間的鴻溝,基于磁盤(pán)的數(shù)據(jù)庫(kù)系統(tǒng)通常使用緩沖池記錄來(lái)提高數(shù)據(jù)庫(kù)的的整體性能。
緩沖池其實(shí)就是通過(guò)內(nèi)存的速度來(lái)彌補(bǔ)磁盤(pán)速度較慢對(duì)數(shù)據(jù)庫(kù)性能的影響。在數(shù)據(jù)庫(kù)進(jìn)行讀取操作時(shí),首先將磁盤(pán)中的頁(yè)放入緩沖池中,下次再讀取相同頁(yè)時(shí),首先從緩沖池中獲取該頁(yè)數(shù)據(jù),起到高速緩存的作用。
數(shù)據(jù)的修改操作,則首先修改在緩沖池中的頁(yè)數(shù)據(jù),然后使用一種稱(chēng)為Checkpoint的機(jī)制刷新到磁盤(pán)上。
緩沖池的大小直接影響數(shù)據(jù)庫(kù)的整體性能,對(duì)于InnoDB存儲(chǔ)引擎而言,緩沖池配置通過(guò)參數(shù) innodb_buffer_pool_size 來(lái)設(shè)置。使用 SHOW VARIABLES LIKE 'innodb_buffer_pool_size' 命令可查看緩沖池配置:
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size' \G
*************************** 1. row ***************************
Variable_name: innodb_buffer_pool_size
Value: 134217728
1 row in set (0.01 sec)
緩沖池中緩存的數(shù)據(jù)頁(yè)類(lèi)型有: 索引頁(yè)、undo頁(yè)、插入緩沖、自適應(yīng)哈希索引、InnoDB鎖信息、數(shù)據(jù)字典信息等,索引頁(yè)和數(shù)據(jù)頁(yè)占緩沖池很大的一部分。
重做日志緩沖
緩沖池中的頁(yè)數(shù)據(jù)比磁盤(pán)要新時(shí),需要將新數(shù)據(jù)刷新到磁盤(pán)中。InnoDB采用Write Ahead Log策略來(lái)刷新數(shù)據(jù),即當(dāng)事務(wù)提交時(shí),先寫(xiě)入重做日志緩沖,重做日志緩沖會(huì)按一定頻率刷新到重置日志文件中,然后臟頁(yè)會(huì)根據(jù)checkpoint機(jī)制刷新到磁盤(pán)。
重做日志緩沖不需要設(shè)置很大,通常情況下8M能滿足大部分的應(yīng)用場(chǎng)景。重做日志支持一下三種情況觸發(fā)刷新:
- Master Thread每一秒將重做日志緩沖刷新到重做日志文件
- 每次事務(wù)提交時(shí)將重做日志緩沖刷新到重做日志文件
- 當(dāng)重做日志緩沖池剩余空間小于1/2時(shí),重做日志緩沖刷新到重做日志文件
額外內(nèi)存池
在InnoDB存儲(chǔ)引擎中,對(duì)內(nèi)存的管理是通過(guò)一種稱(chēng)為內(nèi)存堆的方式進(jìn)行的。在對(duì)一些數(shù)據(jù)結(jié)構(gòu)本身的內(nèi)存進(jìn)行分配時(shí),需要從額外的內(nèi)存池中進(jìn)行申請(qǐng),當(dāng)該區(qū)域的內(nèi)存不夠時(shí),會(huì)從緩沖池中進(jìn)行申請(qǐng)。
鎖
InnoDB支持的鎖有:
共享鎖和排他鎖
InnoDB引擎實(shí)現(xiàn)了兩種標(biāo)準(zhǔn)的行級(jí)鎖,共享鎖(shared (S) locks) 和排他鎖 (exclusive (X) locks)。共享鎖允許一個(gè)占有鎖的事務(wù)去讀取一行數(shù)據(jù),排它鎖則允許事務(wù)對(duì)某一行記錄進(jìn)行寫(xiě)操作。
如果一個(gè)事務(wù)持有了一個(gè)共享鎖,其他事務(wù)仍然可以獲取這行記錄的共享鎖,但不能獲取到這行記錄的排它鎖。當(dāng)一個(gè)事務(wù)獲取到了某一行的排它鎖,則其他事務(wù)將無(wú)法再獲取這行記錄的共享鎖和排它鎖。
意向鎖
在InnoDB中,意向鎖是一種表級(jí)鎖,分為共享鎖和排他鎖:
- 意向共享鎖:將要去獲取某一行的共享鎖
- 意向排它鎖:將要去獲取某一行的排它鎖
事務(wù)在獲取共享/排它鎖之前必須先獲取意向共享/排它鎖,意向鎖不會(huì)阻塞其他任何對(duì)表的操作,他只是告訴其他事務(wù)他將要去獲取某一行的共享鎖或者排他鎖。
記錄鎖
記錄是是作用在索引上的一種鎖,他鎖住的是某一條記錄的索引而非記錄本身,如果當(dāng)前表沒(méi)有索引那么InnoDB將會(huì)為其創(chuàng)建一個(gè)隱藏的聚集索引,而Record Locks將會(huì)鎖住這個(gè)隱藏的聚集索引。
間隙鎖
間隙鎖和記錄鎖一樣也是作用在索引上,不同的是記錄鎖只作用于一條索引記錄而間隙鎖可以鎖住一個(gè)范圍內(nèi)的索引。間隙鎖在InnoDB的唯一作用就是防止其他事務(wù)的插入操作,以此防止幻讀的發(fā)生。
自增鎖
自增鎖是一種特殊的表級(jí)鎖,他只作用在包含自增列的插入操作時(shí)。當(dāng)一個(gè)事務(wù)正在插入一條數(shù)據(jù)時(shí),其他的任何事務(wù)都必須等待整個(gè)事務(wù)完成插入操作,在取獲取鎖來(lái)執(zhí)行插入操作。
事務(wù)
ACID
事務(wù)是數(shù)據(jù)庫(kù)作為OLTP最為重要的特性,說(shuō)起事務(wù)不得不提起ACID四個(gè)基本特性:
- 原子性(Atomicity) :事務(wù)最小工作單元,要么全成功,要么全失敗
- 一致性(Consistency): 事務(wù)開(kāi)始和結(jié)束后,數(shù)據(jù)庫(kù)的完整性不會(huì)被破壞
- 隔離性(Isolation) :不同事務(wù)之間互不影響,四種隔離級(jí)別為RU(讀未提交)、RC(讀已提交)、RR(可重復(fù)讀)、SERIALIZABLE (串行化)
- 持久性(Durability) :事務(wù)提交后,對(duì)數(shù)據(jù)的修改是永久性的,即使系統(tǒng)故障也不會(huì)丟失
InnoDB的原子性、持久性和一致性主要是通過(guò)Redo Log、Undo Log和Force Log at Commit機(jī)制機(jī)制來(lái)完成的。Redo Log用于在崩潰時(shí)恢復(fù)數(shù)據(jù),Undo Log用于對(duì)事務(wù)的影響進(jìn)行撤銷(xiāo),也可以用于多版本控制。而Force Log at Commit機(jī)制保證事務(wù)提交后Redo Log日志都已經(jīng)持久化。隔離性則是由鎖和MVCC來(lái)保證的。
隔離級(jí)別
在MySQL中,事務(wù)有4種隔離級(jí)別,分別是:
- Read Uncommitted 未提交讀
- Read Committed 已提交讀
- Repeatable Read 可重復(fù)讀
- Serializable 可串行化
在理解四種隔離級(jí)別之前,我們需要先了解另外三個(gè)名詞:
a事務(wù)會(huì)讀取到b事務(wù)還未提交的數(shù)據(jù),但是b事務(wù)由于某種原因進(jìn)行回滾操作,這樣,a事務(wù)讀取的數(shù)據(jù)是不可用的,進(jìn)而會(huì)造成一些異常結(jié)果。
a事務(wù)周期內(nèi)對(duì)某一數(shù)據(jù)多次查詢,同時(shí)這些數(shù)據(jù)在b事務(wù)中進(jìn)行了update或delete操作。那么a事務(wù)每次查詢出來(lái)的結(jié)果可能都不一樣。
幻讀的結(jié)果其實(shí)和不可重復(fù)讀是一樣的表現(xiàn),差異就在于不可重復(fù)讀主要是針對(duì)其他事務(wù)進(jìn)行了編輯(update)和刪除(delete)操作。而幻讀主要是針對(duì)插入(insert)操作。也就是在一個(gè)事務(wù)生命周期內(nèi),會(huì)查詢到另外一個(gè)事務(wù)新插入的數(shù)據(jù)。
Read uncommitted 未提交讀
未提交讀,這種情況下,一個(gè)事務(wù)a可以看到另一個(gè)事務(wù)b未提交的數(shù)據(jù),如果此時(shí)事務(wù)b發(fā)生回滾,那么事務(wù)a拿到的就是臟數(shù)據(jù),這也就是臟讀的含義。
此隔離級(jí)別在MySQL InnoDB一般不推薦使用。
Read Committed 已提交讀
已提交讀,一個(gè)事務(wù)從開(kāi)始直到提交之前,所做的任何修改對(duì)其他事務(wù)都是不可見(jiàn)的。解決了臟讀問(wèn)題,但是存在幻讀現(xiàn)象。
Repeatable Read 可重復(fù)讀
可重復(fù)讀,該級(jí)別保證在同一事務(wù)中多次讀取同樣記錄的結(jié)果是一致的,在InnoDB存儲(chǔ)引擎中同時(shí)解決了幻讀和不可重復(fù)讀問(wèn)題。
InnoDB引擎通過(guò)使用Next-Key Lock解決了幻讀的問(wèn)題。Next-Key Lock是行鎖和間隙鎖的組合,當(dāng)InnoDB掃描索引記錄的時(shí)候,會(huì)首先對(duì)索引記錄加上行鎖(Record Lock),再對(duì)索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。加上間隙鎖之后,其他事務(wù)就不能在這個(gè)間隙修改或者插入記錄。
Serializable 可串行化
Serializable 是最高的隔離級(jí)別,它通過(guò)強(qiáng)制事務(wù)串行執(zhí)行,避免了幻讀的問(wèn)題,但是 Serializable 會(huì)在讀取的每一行數(shù)據(jù)上都加鎖,所以可能導(dǎo)致大量的超時(shí)和鎖爭(zhēng)用的問(wèn)題,因此并發(fā)度急劇下降,在MySQL InnoDB同樣不被建議使用。
開(kāi)啟事務(wù)
- BEGIN、BEGIN WORK、START TRANSACTION
執(zhí)行BEGIN命令不會(huì)真正在引擎層開(kāi)啟新事務(wù),僅僅是為當(dāng)前線程設(shè)定標(biāo)記,表示為顯式開(kāi)啟的事務(wù)。
- START TRANSACTION READ ONLY
開(kāi)啟只讀事務(wù),當(dāng)MySQL Server接收到任何數(shù)據(jù)更改的SQL時(shí),都會(huì)直接拒絕修改并返回錯(cuò)誤,此錯(cuò)我不會(huì)進(jìn)入引擎層。
- START TRANSACTION READ WRITE
允許super用戶在當(dāng)前線程只讀狀態(tài)為true的情況下啟動(dòng)讀寫(xiě)事務(wù)。
- START TRANSACTION WITH CONSISTENT SNAPSHOT
開(kāi)啟事務(wù)會(huì)進(jìn)入引擎層,并開(kāi)啟一個(gè)readview。只有在RR隔離級(jí)別下,這種操作才有效,否則會(huì)報(bào)錯(cuò)。
Undo log
在數(shù)據(jù)進(jìn)行修改時(shí)會(huì)記錄相應(yīng)的undo日志,如果事務(wù)失敗或者回滾,可以借助記錄的undo日志進(jìn)行回滾。Undo log是邏輯日志,記錄更改前的數(shù)據(jù)鏡像。在修改時(shí)如果同時(shí)需要讀取當(dāng)前數(shù)據(jù)的時(shí)候,它可以根據(jù)版本信息分析出該行記錄以前版本的數(shù)據(jù)。另外Undo log也會(huì)產(chǎn)生重做日志,因?yàn)閁ndo log也要進(jìn)行持久化保護(hù)。
事務(wù)提交
- 使用全局事務(wù)ID產(chǎn)生器生成事務(wù)NO,將當(dāng)前連接的事務(wù)指針(trx_t)添加到全局提交事務(wù)鏈表(trx_serial_list)中
- 標(biāo)記undo,如果這個(gè)事務(wù)只使用了一個(gè)UndoPage且使用量小于3/4個(gè)Page,則把這個(gè)Page標(biāo)記為T(mén)RX_UNDO_CACHED,如果不滿足且是insert undo則標(biāo)記為T(mén)RX_UNDO_TO_FREE,否則undo為update undo則標(biāo)記為T(mén)RX_UNDO_TO_PURGE。標(biāo)記為T(mén)RX_UNDO_CACHED的undo會(huì)被引擎回收。
- 把update undo放入所在undo segment的history list,并遞增rseg_history_len(全局)。同時(shí)更新Page上的TRX_UNDO_TRX_NO, 如果刪除了數(shù)據(jù),則重置delete_mark
- 把undate undo從update_undo_list中刪除,如果被標(biāo)記為T(mén)RX_UNDO_CACHED,則加入到update_undo_cached隊(duì)列中
- mtr_commit(日志undo/redo寫(xiě)入公共緩沖區(qū)),至此,在文件層次事務(wù)提交。這個(gè)時(shí)候即使crash,重啟后依然能保證事務(wù)是被提交的。接下來(lái)要做的是內(nèi)存數(shù)據(jù)狀態(tài)的更新(trx_commit_in_memory)
- 只讀事務(wù)只需要把readview從全局readview鏈表中移除,然后重置trx_t結(jié)構(gòu)體里面的信息即可。讀寫(xiě)事務(wù)首先需要是設(shè)置事務(wù)狀態(tài)為T(mén)RX_STATE_COMMITTED_IN_MEMORY,釋放所有行鎖并且將trx_t從rw_trx_list中移除,readview從全局readview鏈表中移除。如果有insert undo則在這里移除,如果有update undo則喚醒Purge線程進(jìn)行垃圾清理,最后重置trx_t里的信息,便于下一個(gè)事務(wù)使用
回滾
- 如果是只讀事務(wù),則直接返回
- 判斷當(dāng)前是回滾整個(gè)事務(wù)還是部分事務(wù),如果是部分事務(wù),則記錄下需要保留多少個(gè)Undo log,多余的全進(jìn)行回滾
- 從update undo和insert undo中找出最后一條undo,從這條undo開(kāi)始回滾
- 如果是update undo則將標(biāo)記為刪除的記錄清理標(biāo)記,更新過(guò)的數(shù)據(jù)回滾到最老的版本。如果是insert undo則直接刪除聚集索引和二級(jí)索引
- 如果所有undo都已經(jīng)被回滾或者回滾到了指定的undo則停止,把Undo log刪除
索引
InnoDB引擎使用B+樹(shù)作為索引結(jié)構(gòu),主鍵索引的葉子節(jié)點(diǎn)data域保存了完整的字段數(shù)據(jù),非主鍵索引的葉子節(jié)點(diǎn)保存了指向主鍵的值數(shù)據(jù)。
上圖是 InnoDB 主索引(同時(shí)也是數(shù)據(jù)文件)的示意圖,可以看到葉節(jié)點(diǎn)包含了完整的數(shù)據(jù)記錄,這種索引叫做聚集索引。因?yàn)?InnoDB 的數(shù)據(jù)文件本身要按主鍵聚集,所以 InnoDB 要求表必須有主鍵,如果沒(méi)有顯式指定,則 MySQL 系統(tǒng)會(huì)自動(dòng)選擇一個(gè)可以唯一標(biāo)識(shí)數(shù)據(jù)記錄的列作為主鍵,如果不存在這種列,則 MySQL 自動(dòng)為 InnoDB 表生成一個(gè)隱含字段作為主鍵,這個(gè)字段長(zhǎng)度為6個(gè)字節(jié),類(lèi)型為長(zhǎng)整形。
InnoDB 的輔助索引 data 域存儲(chǔ)相應(yīng)記錄主鍵的值而不是地址。換句話說(shuō),InnoDB 的所有輔助索引都引用主鍵作為 data 域。聚集索引這種實(shí)現(xiàn)方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。
結(jié)尾
對(duì)于MySQL InnoDB的諸多特性,本文只介紹了很小的一部分,感興趣的同學(xué)可閱讀 《MySQL技術(shù)內(nèi)幕:InnoDB存儲(chǔ)引擎》了解更多相關(guān)知識(shí)。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
您可能感興趣的文章:- MySql優(yōu)化之InnoDB,4GB內(nèi)存,多查詢的my.ini中文配置方案詳解
- 簡(jiǎn)述MySQL InnoDB存儲(chǔ)引擎
- MySQL 學(xué)習(xí)總結(jié) 之 初步了解 InnoDB 存儲(chǔ)引擎的架構(gòu)設(shè)計(jì)
- MySQL學(xué)習(xí)(七):Innodb存儲(chǔ)引擎索引的實(shí)現(xiàn)原理詳解
- MySQL存儲(chǔ)引擎MyISAM與InnoDB區(qū)別總結(jié)整理
- 詳解分析MySQL8.0的內(nèi)存消耗
- MySQL 內(nèi)存表和臨時(shí)表的用法詳解
- MySQL8.0內(nèi)存相關(guān)參數(shù)總結(jié)
- MySql減少內(nèi)存占用的方法詳解
- 詳解MySQL InnoDB存儲(chǔ)引擎的內(nèi)存管理