具體細(xì)節(jié) 請(qǐng)去掘金購(gòu)買《MySQL 是怎樣運(yùn)行的:從根兒上理解 MySQL》
InnoDB 的 Buffer Pool
- 1.innodb存儲(chǔ)數(shù)據(jù),都是存放在表空間,表空間實(shí)際對(duì)應(yīng)著一個(gè)或者幾個(gè)的實(shí)際文件。
- 2.訪問數(shù)據(jù)的時(shí)候,innodb只能以頁(yè)為單位。
- 3.innodb通過Buffer pool 把加載進(jìn)入內(nèi)存的頁(yè),緩存起來(lái),避免立即釋放。
Buffer Pool的簡(jiǎn)介
- 1.Mysql服務(wù)器啟動(dòng)的時(shí)候向OS申請(qǐng)一片連續(xù)的內(nèi)存---這個(gè)就是Buffer Pool
- 2.默認(rèn)情況下Buffer Pool只有128M大小,可以通過innodb_buffer_pool_size修改,該參數(shù)的單位是字節(jié)。
- 3.最小值只能是5M。
Buffer Pool內(nèi)部組成
- 1.Buffer Pool中默認(rèn)的緩存頁(yè)大小和在磁盤上默認(rèn)的頁(yè)大小是一樣的,都是16KB
- 2.為了管理Pool中的緩存頁(yè),Innodb為每一個(gè)緩存頁(yè)創(chuàng)建了一些所謂的
控制信息。--控制信息也是寫在頁(yè)上面的。 - 3.控制信息包括該頁(yè)所屬的表空間編號(hào)、頁(yè)號(hào)、緩存頁(yè)在Buffer Pool中的地址、鏈表節(jié)點(diǎn)信息、一些鎖信息以及LSN信息。
- 4.因?yàn)槊總€(gè)緩存頁(yè)對(duì)應(yīng)的控制信息占用的內(nèi)存大小是相同的,因此從buffer pool中分配一塊內(nèi)存專門記錄控制信息--控制塊
- 5.控制塊和緩存頁(yè)是一一對(duì)應(yīng)的,它們都被存放到 Buffer Pool 中,其中控制塊被存放到 Buffer Pool 的前邊,緩存頁(yè)被存放到 Buffer Pool 后邊
- 6.控制塊和緩存頁(yè)之間是有碎片的--當(dāng)然如果大小分配合理也有可能沒有碎片。
- 7.每個(gè)控制塊大約占用緩存頁(yè)大小的5%,innodb_buffer_pool_size設(shè)置的大小并不包含控制塊的大小,也就是說(shuō)InnoDB在為Buffer Pool向操作系統(tǒng)申請(qǐng)連續(xù)的內(nèi)存空間時(shí),這片連續(xù)的內(nèi)存空間一般會(huì)比innodb_buffer_pool_size的值大5%左右。
free鏈表的管理
- 1.Buffer pool中的屬于free的控制塊單獨(dú)組成一個(gè)鏈表(方便尋找free頁(yè))---free鏈表(或者說(shuō)空閑鏈表)
- 2.剛初始化完的buffer pool 就是一個(gè)free鏈表控制塊+碎片+緩存頁(yè)
- 3.為了管理好這個(gè)free鏈表,特意為這個(gè)鏈表定義了一個(gè)基節(jié)點(diǎn),含著鏈表的頭節(jié)點(diǎn)地址,尾節(jié)點(diǎn)地址,以及當(dāng)前鏈表中節(jié)點(diǎn)的數(shù)量等信息
- 4.基節(jié)點(diǎn)的大小不算在bufferpool里面
- 5.當(dāng)從磁盤中加載一個(gè)頁(yè)到Buffer pool中的時(shí)候,從free鏈表中找到控制塊,控制塊初始化會(huì)指向一些空白的緩存頁(yè)。
- 6.當(dāng)緩存頁(yè)被使用之后,該控制塊就被移除了。當(dāng)然在移除之前,會(huì)把該緩存頁(yè)的信息填寫到控制塊上,比如緩存的數(shù)據(jù)所在
的表空間,頁(yè)號(hào)之類的。--控制塊和緩存頁(yè)是釘死的,移除控制塊的意思就是把該控制塊的上一個(gè)控制塊的free_next指向該控制塊的下一個(gè)控制塊。 - 7.
對(duì)應(yīng)的緩存頁(yè)還是留在原來(lái)的位置
緩存頁(yè)的哈希處理
- 1.當(dāng)我們想要訪問某個(gè)頁(yè)中數(shù)據(jù)的時(shí)候,這個(gè)時(shí)候如果緩存存在就訪問緩存,不存在就去磁盤訪問
- 2.如何檢查緩存是否存在?由于表空間和頁(yè)號(hào)是固定的(在生成數(shù)據(jù)的時(shí)候劃分好的,持久化到文件)。然后根據(jù)這個(gè)去hash尋找緩存頁(yè)
- 3.當(dāng)找不到緩存,再去free鏈表分配一個(gè)頁(yè)給磁盤加載數(shù)據(jù)
flush鏈表的管理
- 1.當(dāng)緩存頁(yè)被修改了,這就導(dǎo)致數(shù)據(jù)和磁盤上的頁(yè)不一樣了。
- 2.innodb選擇在一定的時(shí)候進(jìn)行同步
- 3.每個(gè)被修改的緩存頁(yè)最后都會(huì)被加入到flush鏈表。
- 4.flush鏈表和free鏈表相似 都包含控制塊。
- 5.在flush鏈表中的頁(yè)面肯定在LRU鏈表中
LRU鏈表的進(jìn)化
- 1.我們?nèi)隠RU鏈表的都是緩存頁(yè)對(duì)應(yīng)的控制塊(即從Free進(jìn)入了LRU)
- 2.簡(jiǎn)單版本的LRU,因?yàn)镮NNODB的預(yù)讀功能和掃描全表的查詢語(yǔ)句所以被淘汰。
- 3.簡(jiǎn)單LRU進(jìn)化為--劃分冷(old)熱(young)區(qū)域的LRU,該LRU是按照鏈表比例來(lái)區(qū)分的。
- 4.通過innodb_old_blocks_pct確定old區(qū)域在LRU鏈表中所占的比例,默認(rèn)值是37%--全局變量
- 5.劃分區(qū)域的LRU,初始的緩存頁(yè)放在old區(qū)域頭部,不會(huì)影響young區(qū)域。
- 6.通過innodb_old_blocks_time來(lái)控制放入old區(qū)域頭部的數(shù)據(jù),會(huì)在多久時(shí)間之內(nèi)被再次訪問才會(huì)放入young
- 7.如果innodb_old_blocks_time=0,代表數(shù)據(jù)直接被放入young。
- 8.上述的LRU還存在一個(gè)缺點(diǎn),每次訪問一個(gè)緩存頁(yè)就需要把控制塊移動(dòng)到LRU鏈表頭部,開銷太大,因此提出了被訪問的緩存頁(yè)位于young區(qū)域的1/4的后邊,才會(huì)被移動(dòng)到LRU鏈表頭部
線性預(yù)讀
- 1.統(tǒng)變量innodb_read_ahead_threshold,如果順序訪問了某個(gè)區(qū)(extent)的頁(yè)面超過這個(gè)系統(tǒng)變量的值,就會(huì)觸發(fā)一次異步讀取下一個(gè)區(qū)中全部的頁(yè)面到Buffer Pool的請(qǐng)求
- 2.innodb_read_ahead_threshold默認(rèn)值是56,是全局變量。
隨機(jī)預(yù)讀
- 1.如果Buffer Pool中已經(jīng)緩存了某個(gè)區(qū)的13個(gè)連續(xù)的頁(yè)
- 2.不論這些頁(yè)面是不是順序讀取的,都會(huì)觸發(fā)一次異步讀取本區(qū)中所有其的頁(yè)面到Buffer Pool的請(qǐng)求
刷新臟頁(yè)到磁盤
- 1.后臺(tái)有專門的線程每隔一段時(shí)間負(fù)責(zé)把臟頁(yè)刷新到磁盤
- 2.從LRU鏈表的冷數(shù)據(jù)中刷新一部分頁(yè)面到磁盤---定時(shí)從LRU鏈表尾部開始掃描一些頁(yè)面,掃描的頁(yè)面數(shù)量可以通過系統(tǒng)變量innodb_lru_scan_depth來(lái)指定
- 3.從flush鏈表中刷新一部分頁(yè)面到磁盤--定時(shí)從flush鏈表中刷新一部分頁(yè)面到磁盤,刷新的速率取決于當(dāng)時(shí)系統(tǒng)是不是很繁忙
- 4.如果刷新較慢導(dǎo)致沒有緩存頁(yè)可用只能替換掉LRU尾部數(shù)據(jù),先去尋找沒有修改記錄的,找不到再去找臟頁(yè)并同步到磁盤。
- 5.如果系統(tǒng)特別繁忙 也可能出現(xiàn)用戶線程批量的從flush鏈表中刷新臟頁(yè)
- 6.
注意這邊的刷新到磁盤還是會(huì)遇到斷電因素,因此原子性是redo和undo的事情(他們都是順序?qū)懙模?/code>
多個(gè)Buffer Pool實(shí)例
- 1.在多線程情況下訪問buffer pool中的鏈表是需要加鎖的,因此可以通過配置多個(gè)pool,來(lái)降低鎖開銷。
- 2.多個(gè)bufferpool的實(shí)例之間大小都是一樣的。通過innodb_buffer_pool_instances ,來(lái)設(shè)置bufferpool的個(gè)數(shù)。
- 3.當(dāng)innodb_buffer_pool_size的值小于1G的時(shí)候設(shè)置多個(gè)實(shí)例是無(wú)效的,InnoDB會(huì)默認(rèn)把innodb_buffer_pool_instances 的值修改為1
innodb_buffer_pool_chunk_size
- 1.為了支持mysql運(yùn)行時(shí)候可以調(diào)整buffer pool的大小(通過innodb_buffer_pool_size)
- 2.動(dòng)態(tài)調(diào)整的話如果要在運(yùn)行過程中重新申請(qǐng)一塊連續(xù)的內(nèi)存,負(fù)責(zé)老的數(shù)據(jù)到新的pool,太損耗性能了。
- 3.因此mysql改為以一個(gè)chunk為單位去申請(qǐng)內(nèi)存,若干個(gè)chunk就是bufferpool。所以動(dòng)態(tài)調(diào)整的時(shí)候只需要
增減chunk就行。 - 4.innodb_buffer_pool_chunk_size就是指定chunk的大小。
- 5.innodb_buffer_pool_chunk_size的值只能在服務(wù)器啟動(dòng)時(shí)指定,在服務(wù)器運(yùn)行過程中是不可以修改的。
- 6.因此在運(yùn)行過程中通過innodb_buffer_pool_size來(lái)調(diào)整bufferpool的大小。
配置Buffer Pool時(shí)的注意事項(xiàng)
- 1.innodb_buffer_pool_size必須是innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances的倍數(shù)
Buffer Pool中存儲(chǔ)的其它信息
- 1.Buffer Pool的緩存頁(yè)除了用來(lái)緩存磁盤上的頁(yè)面以外,還可以存儲(chǔ)鎖信息、自適應(yīng)哈希索引等信息
總結(jié)
- 1.不論是LRU,F(xiàn)LUSH還是Free鏈表,其都是邏輯鏈表。
- 2.底層的物理內(nèi)存中控制塊和數(shù)據(jù)頁(yè)都是在一起的。
- 3.SHOW ENGINE INNODB STATUS可以查看到Buffer Pool的信息