mysql+缓冲池脏块率高_什么是数据库的 “缓存池” ?(万字干货)
原標(biāo)題:什么是數(shù)據(jù)庫(kù)的 “緩存池” ?(萬(wàn)字干貨)
1、Buffer Pool 概述
Buffer Pool 是什么?從字面上看是 緩存池 的意思,沒錯(cuò),它其實(shí)也就是 緩存池 的意思。它是 MySQL 當(dāng)中至關(guān)重要的一個(gè)組件,可以這么說(shuō), MySQL的所有的增刪改的操作都是在 Buffer Pool 中執(zhí)行的。
但是數(shù)據(jù)不是在磁盤中的嗎?怎么會(huì)和緩存池又有什么關(guān)系呢?那是因?yàn)槿绻?MySQL的操作都在磁盤中進(jìn)行,那很顯然效率是很低的,效率為什么低?因?yàn)閿?shù)據(jù)庫(kù)要從磁盤中拿數(shù)據(jù)啊,那肯定就需要IO啊,并且數(shù)據(jù)庫(kù)并不知道它將要查找的數(shù)據(jù)是磁盤的哪個(gè)位置,所以這就需要進(jìn)行隨機(jī)IO,那這個(gè)性能簡(jiǎn)直就別玩了。所以 MySQL對(duì)數(shù)據(jù)的操作都是在內(nèi)存中進(jìn)行的,也就是在 Buffer Pool 這個(gè)內(nèi)存組件中。
實(shí)際上他就好比是 Redis,因?yàn)?Redis 是一個(gè)內(nèi)存是數(shù)據(jù)庫(kù),他的操作就都是在內(nèi)存中進(jìn)行的,并且會(huì)有一定的策略將其持久化到磁盤中。那 Buffer Pool 的內(nèi)存結(jié)構(gòu)具體是什么樣子的,那么多的增刪改操作難道數(shù)據(jù)要一直在內(nèi)存中嗎?既然說(shuō)類似 redis 緩存,那是不是也像 redis 一樣也有一定的淘汰策略呢?
本篇文章,會(huì)詳細(xì)的介紹 Buffer Pool 的內(nèi)存結(jié)構(gòu),讓大家徹底明白這里面的每一步執(zhí)行流程。我們先看一下 MySQL從加載磁盤文件到完成提交一個(gè)事務(wù)的整個(gè)流程。我們先來(lái)看一個(gè)總體的流程圖,從數(shù)據(jù)在磁盤中被加載到緩存池中,然后經(jīng)過一些列的操作最終又被刷入到磁盤的一個(gè)過程,都經(jīng)歷了哪些事情,這個(gè)圖不明白沒有關(guān)系,因?yàn)楸疚闹攸c(diǎn)是 Buffer Pool 這個(gè)整體的流程就是讓大家稍微有個(gè)印象。
2、Buffer Pool 有多大
# 查看和調(diào)整innodb_buffer_pool_size
1.查看@@innodb_buffer_pool_size大小,單位字節(jié)
SELECT@ @innodb_buffer_pool_size/ 1024/ 1024/ 1024; #字節(jié)轉(zhuǎn)為G
2.在線調(diào)整InnoDB緩沖池大小,如果不設(shè)置,默認(rèn)為128M
setglobalinnodb_buffer_pool_size = 4227858432; ##單位字節(jié)
他在 InnoDB 中的整體結(jié)構(gòu)大概是這樣子的
3、數(shù)據(jù)頁(yè)
剛剛介紹到 MySQL在執(zhí)行增刪改的時(shí)候數(shù)據(jù)是會(huì)被加載到 Buffer Pool 中的,既然這樣數(shù)據(jù)是怎么被加載進(jìn)來(lái)的,是一條一條還是說(shuō)是以其他的形式呢。我們操作的數(shù)據(jù)都是以 表 + 行 的方式,而 表 + 行 僅僅是邏輯上的概念, MySQL并不會(huì)像我們一樣去操作行數(shù)據(jù),而是抽象出來(lái)一個(gè)一個(gè)的 數(shù)據(jù)頁(yè) 概念,每個(gè)數(shù)據(jù)頁(yè)的大小默認(rèn)是 16KB,這些參數(shù)都是可以調(diào)整的。但是建議使用默認(rèn)的就好,畢竟 MySQL能做到極致的都已經(jīng)做了。每個(gè)數(shù)據(jù)頁(yè)存放著多條的數(shù)據(jù), MySQL在執(zhí)行增刪改首先會(huì)定位到這條數(shù)據(jù)所在數(shù)據(jù)頁(yè),然后會(huì)將 數(shù)據(jù)所在的數(shù)據(jù)頁(yè)加載到 Buffer Pool 中。
4、緩存頁(yè)
當(dāng)數(shù)據(jù)頁(yè)被加載到緩沖池中后,Buffer Pool 中也有叫 緩存頁(yè) 的概念與其一一對(duì)應(yīng),大小同樣是 16KB,但是 MySQL還為每個(gè)緩存也開辟額外的一些空間,用來(lái)描述對(duì)應(yīng)的緩存頁(yè)的一些信息,例如:數(shù)據(jù)頁(yè)所屬的表空間,數(shù)據(jù)頁(yè)號(hào),這些描述數(shù)據(jù)塊的大小大概是緩存頁(yè)的15%左右(約800B)。
# 緩存頁(yè)是什么時(shí)候被創(chuàng)建的?
當(dāng) MSql 啟動(dòng)的時(shí)候,就會(huì)初始化 Buffer Pool,這個(gè)時(shí)候 MySQL 會(huì)根據(jù)系統(tǒng)中設(shè)置的 innodb_buffer_pool_size 大小去內(nèi)存中申請(qǐng)一塊連續(xù)的內(nèi)存空間,實(shí)際上在這個(gè)內(nèi)存區(qū)域比配置的值稍微大一些,因?yàn)椤久枋鰯?shù)據(jù)】也是占用一定的內(nèi)存空間的,當(dāng)在內(nèi)存區(qū)域申請(qǐng)完畢之后, MySql 會(huì)根據(jù)默認(rèn)的緩存頁(yè)的大小( 16KB)和對(duì)應(yīng)`緩存頁(yè)* 15%`大小( 800B左右)的數(shù)據(jù)描述的大小,將內(nèi)存區(qū)域劃分為一個(gè)個(gè)的緩存頁(yè)和對(duì)應(yīng)的描述數(shù)據(jù)
5、Free鏈表
上面是說(shuō)了每個(gè)數(shù)據(jù)頁(yè)會(huì)被加載到一個(gè)緩存頁(yè)中,但是加載的時(shí)候 MySQL是如何知道那個(gè)緩存頁(yè)有數(shù)據(jù),那個(gè)緩存頁(yè)沒有數(shù)據(jù)呢?換句話說(shuō), MySQL是怎么區(qū)分哪些緩存頁(yè)是空閑的狀態(tài),是可以用來(lái)存放數(shù)據(jù)頁(yè)的。
為了解決這個(gè)問題, MySQL為 Buffer Pool 設(shè)計(jì)了一個(gè)雙向鏈表— free鏈表,這個(gè) free 鏈表的作用就是用來(lái)保存空閑緩存頁(yè)的描述塊(這句話這么說(shuō)其實(shí)不嚴(yán)謹(jǐn),換句話:每個(gè)空閑緩存頁(yè)的描述數(shù)據(jù)組成一個(gè)雙向鏈表,這個(gè)鏈表就是free鏈表)。之所以說(shuō) free鏈表的作用就是用來(lái)保存空閑緩存頁(yè)的描述數(shù)據(jù) 是為了先讓大家明白 free 鏈表的作用,另外 free 鏈表還會(huì)有一個(gè) 基礎(chǔ)節(jié)點(diǎn) ,他會(huì)引用該鏈表的頭結(jié)點(diǎn)和尾結(jié)點(diǎn),還會(huì)記錄節(jié)點(diǎn)的個(gè)數(shù)(也就是可用的空閑的緩存頁(yè)的個(gè)數(shù))。
這個(gè)時(shí)候,他可以用下面的圖片來(lái)描述:
當(dāng)加載數(shù)據(jù)頁(yè)到緩存池中的時(shí)候, MySQL會(huì)從 free 鏈表中獲取一個(gè)描述數(shù)據(jù)的信息,根據(jù)描述節(jié)點(diǎn)的信息拿到其對(duì)應(yīng)的緩存頁(yè),然后將數(shù)據(jù)頁(yè)信息放到該緩存頁(yè)中,同時(shí)將鏈表中的該描述數(shù)據(jù)的節(jié)點(diǎn)移除。這就是數(shù)據(jù)頁(yè)被讀取 Buffer Pool 中的緩存頁(yè)的過程。
但 MySQL是怎么知道哪些數(shù)據(jù)頁(yè)已經(jīng)被緩存了,哪些沒有被緩存呢。實(shí)際上數(shù)據(jù)庫(kù)中還有后一個(gè) 哈希表結(jié)構(gòu) ,他的作用是用來(lái)存儲(chǔ) 表空間號(hào) + 數(shù)據(jù)頁(yè)號(hào) 作為數(shù)據(jù)頁(yè)的key,緩存頁(yè)對(duì)應(yīng)的地址作為其value,這樣數(shù)據(jù)在加載的時(shí)候就會(huì)通過哈希表中的key來(lái)確定數(shù)據(jù)頁(yè)是否被緩存了。
6、Flush鏈表
MySql 在執(zhí)行增刪改的時(shí)候會(huì)一直將數(shù)據(jù)以數(shù)據(jù)頁(yè)的形式加載到 Buffer Pool 的緩存頁(yè)中,增刪改的操作都是在內(nèi)存中執(zhí)行的,然后會(huì)有一個(gè)后臺(tái)的線程數(shù)將臟數(shù)據(jù)刷新到磁盤中,但是后臺(tái)的線程肯定是需要知道應(yīng)該刷新哪些啊。
針對(duì)這個(gè)問題, MySQL設(shè)計(jì)出了 Flush 鏈表,他的作用就是記錄被修改過的臟數(shù)據(jù)所在的緩存頁(yè)對(duì)應(yīng)的描述數(shù)據(jù)。如果內(nèi)存中的數(shù)據(jù)和數(shù)據(jù)庫(kù)和數(shù)據(jù)庫(kù)中的數(shù)據(jù)不一樣,那這些數(shù)據(jù)我們就稱之為 臟數(shù)據(jù) ,臟數(shù)據(jù)之所以叫臟數(shù)據(jù),本質(zhì)上就是被緩存到緩存池中的數(shù)據(jù)被修改了,但是還沒有刷新到磁盤中。
同樣的這些已經(jīng)被修改了的數(shù)據(jù)所在的緩存頁(yè)的描述數(shù)據(jù)會(huì)被維護(hù)到 Flush 中(其實(shí)結(jié)構(gòu)和 free 鏈表是一樣的),所以 Flush 中維護(hù)的是一些 臟數(shù)據(jù) 數(shù)據(jù)描述(準(zhǔn)確地說(shuō)是臟數(shù)據(jù)的所在的緩存頁(yè)的數(shù)據(jù)描述)
另外,當(dāng)某個(gè)臟緩存頁(yè)被刷新到磁盤后,其空間就騰出來(lái)了,然后又會(huì)跑到 Free 鏈表中了。
7、LRU鏈表
如果系統(tǒng)一直在進(jìn)行數(shù)據(jù)庫(kù)的增刪改操作,數(shù)據(jù)庫(kù)內(nèi)部的基本流程就是:
我們還拿 redis 類做類比,以便更好的幫助大家明白其原理。Flush 的作用其實(shí)類似 redis 的 key 設(shè)置的過期時(shí)間,所以一般情況下,redis 內(nèi)存不會(huì)不夠使用,但是總有特殊的情況,問題往往就是在這種極端和邊邊角角的情況下產(chǎn)生的。
如果 redis 的內(nèi)存不夠使用了,是不是自己還有一定的淘汰策略?最基本的準(zhǔn)則就是淘汰掉不經(jīng)常使用到的key。Buffer Pool 也類似,它也會(huì)有內(nèi)存不夠使用的情況,它是通過 LRU 鏈表來(lái)維護(hù)的。LRU 即 Least Recently Uesd(最近最少使用)。
MySql 會(huì)把最近使用最少的緩存頁(yè)數(shù)據(jù)刷入到磁盤去,那 MySql 如何判斷出 LRU 數(shù)據(jù)的呢?為此 MySql 專門設(shè)計(jì)了 LUR 鏈表,還引入了另一個(gè)概念: 緩存命中率
# 緩存命中率
可以理解為緩存被使用到的頻率,舉個(gè)例子來(lái)說(shuō):現(xiàn)在有兩個(gè)緩存頁(yè),在100次請(qǐng)求中A緩存頁(yè)被命中了20次,B緩存頁(yè)被命中了2次,很顯然A緩存頁(yè)的命中率更高,這也就意味著A在未來(lái)還會(huì)被使用到的可能性比較大,而B就會(huì)被 MySQL 認(rèn)為基本不會(huì)被使用到;
說(shuō)到這里,那LRU究竟是怎么工作的。假設(shè) MySQL在將數(shù)據(jù)加載到緩存池的時(shí)候,他會(huì)將被加載進(jìn)來(lái)的緩存頁(yè)按照被加載進(jìn)來(lái)的順序插入到LRU鏈表的頭部(就是鏈表的頭插法),假設(shè) MySQL現(xiàn)在先后分別加載A、B、C數(shù)據(jù)頁(yè)到緩存頁(yè)A、B、C中,然后 LRU 的鏈表大致是這樣子的。
現(xiàn)在又來(lái)了一個(gè)請(qǐng)求,假設(shè)查詢到的數(shù)據(jù)是已經(jīng)被緩存在緩存頁(yè)B中,這時(shí)候 MySQL就會(huì)將B緩存頁(yè)對(duì)應(yīng)的描述信息插入到LRU鏈表的頭部,如下圖:
然后又來(lái)了一個(gè)請(qǐng)求,數(shù)據(jù)是已經(jīng)被緩存在了緩存頁(yè)C中,然后LRU會(huì)變成這樣子:
說(shuō)到底,每次查詢數(shù)據(jù)的時(shí)候如果數(shù)據(jù)已經(jīng)在緩存頁(yè)中,那么就會(huì)將該緩存頁(yè)對(duì)應(yīng)的描述信息放到LRU鏈表的頭部,如果不在緩存頁(yè)中,就去磁盤中查找,如果查找到了,就將其加載到緩存中,并將該數(shù)據(jù)對(duì)應(yīng)的緩存頁(yè)的描述信息插入到LRU鏈表的頭部。也就是說(shuō)最近使用的緩存頁(yè)都會(huì)排在前面,而排在后面的說(shuō)明是不經(jīng)常被使用到的。
最后,如果 Buffer Pool 不夠使用了,那么 MySQL就會(huì)將 LRU 鏈表中的 尾節(jié)點(diǎn) 刷入到磁盤中,用來(lái)給 Buffer Pool 騰出內(nèi)存空間。來(lái)個(gè)整體的流程圖給大家看下
8、LRU鏈表帶來(lái)的麻煩
這里的麻煩指的是就是 MySQL本身的預(yù)讀機(jī)制帶來(lái)的問題
# 預(yù)讀機(jī)制
MySQL在從磁盤加載數(shù)據(jù)的的時(shí)候,會(huì)將數(shù)據(jù)頁(yè)的相鄰的其他的數(shù)據(jù)頁(yè)也加載到緩存中。
# MySQL 為什么要這么做
因?yàn)楦鶕?jù)經(jīng)驗(yàn)和習(xí)慣,一般查詢數(shù)據(jù)的時(shí)候往往還會(huì)查詢?cè)摂?shù)據(jù)相鄰前后的一些數(shù)據(jù),有人可能會(huì)反問:一個(gè)數(shù)據(jù)頁(yè)上面不是就會(huì)存在該條數(shù)據(jù)相鄰的數(shù)據(jù)嗎?這可不一定,某條數(shù)據(jù)可能很大,也可能這條數(shù)據(jù)是在數(shù)據(jù)頁(yè)在頭部,也可能是在數(shù)據(jù)頁(yè)的尾部,所以 MySQL 為了提高效率,會(huì)將某個(gè)數(shù)據(jù)頁(yè)的相鄰的數(shù)據(jù)頁(yè)也加載到緩存池中。
上圖能夠看到B的相鄰也被加載到了C描述數(shù)據(jù)的前面,而實(shí)際上C的命中率比B的相鄰頁(yè)高多了,這就是LRU本身帶來(lái)的問題。
# 哪些情況會(huì)觸發(fā)預(yù)讀機(jī)制
1. 有一個(gè)參數(shù)是 innodb_ read_ahead_threshold, 他的默認(rèn)值是56,意思就是如果順序的訪問了一個(gè)區(qū)里的多個(gè)數(shù)據(jù)頁(yè),訪問的數(shù)據(jù)頁(yè)的數(shù)量超過了這個(gè)閾值,此時(shí)就會(huì)觸發(fā)預(yù)讀機(jī)制,把下一個(gè)相鄰區(qū)中的所有數(shù)據(jù)頁(yè)都加載到緩存里去(這種就是:線性預(yù)讀)
2. 如果 Buffer Pool 里緩存了一個(gè)區(qū)里的13個(gè)連續(xù)的數(shù)據(jù)頁(yè),而且這些數(shù)據(jù)頁(yè)都是比較頻繁會(huì)被訪問的,此時(shí)就會(huì)直接觸發(fā)預(yù)讀機(jī)制,把這個(gè)區(qū)里的其他的數(shù)據(jù)頁(yè)都加載到緩存里去(這種就是:隨機(jī)預(yù)讀)隨機(jī)預(yù)讀是通過:innodb_random_read_ahead 來(lái)控制的,默認(rèn)是 OFF即關(guān)閉的(MySQL 5.5已經(jīng)基本飛起該功能,應(yīng)為他會(huì)帶來(lái)不必要的麻煩,這里也不推薦大家開啟,說(shuō)出來(lái)的目的是讓大家了解下有這么個(gè)東西)
還有一種情況是 SELECT * FROM students 這種直接全表掃描的,會(huì)直接加載表中的所有的數(shù)據(jù)到緩存中,這些數(shù)據(jù)基本是加載的時(shí)候查詢一次,后面就基本使用不到了,但是加載這么多數(shù)據(jù)到鏈表的頭部就將其他的經(jīng)常命中的緩存頁(yè)直接全擠到后面去了。
以上種種跡象表明,預(yù)讀機(jī)制帶來(lái)的問題還是蠻大的,既然這么大,那 MySQL為什么還要進(jìn)入預(yù)讀機(jī)制呢,說(shuō)到底還是為了提高效率,** 一種新的技術(shù)的引進(jìn),往往帶來(lái)新的挑戰(zhàn)**,下面我們就一起來(lái)看下 MySQL是如何解決預(yù)加載所帶來(lái)的麻煩的。
9、基于冷熱數(shù)據(jù)分離的LRU鏈表
所謂的冷熱分離,就是將 LRU 鏈表分成兩部分,一部分是經(jīng)常被使用到的 熱數(shù)據(jù) ,另一部分是被加載進(jìn)來(lái)但是很少使用的 冷數(shù)據(jù) 。通過參數(shù) innodb_old_blocks_pct 參數(shù)控制的,默認(rèn)為37,也就是 37% 。用圖表示大致如下:
數(shù)據(jù)在從磁盤被加載到緩存池的時(shí)候,首先是會(huì)被放在冷數(shù)據(jù)區(qū)的頭部,然后在一定時(shí)間之后,如果再次訪問了這個(gè)數(shù)據(jù),那么這個(gè)數(shù)據(jù)所在的緩存頁(yè)對(duì)應(yīng)描述數(shù)據(jù)就會(huì)被放轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表的頭部。
那為什么說(shuō)是在 一定的時(shí)間之后呢,假設(shè)某條數(shù)據(jù)剛被加載到緩存池中,然后緊接著又被訪問了一次,這個(gè)時(shí)候假設(shè)就將其轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表的頭部,但是以后就再也不會(huì)被使用了,這樣子是不是就還是會(huì)存在之前的問題呢?
所以 MySQL通過 innodb_old_blocks_time 來(lái)設(shè)置數(shù)據(jù)被加載到緩存池后的多少時(shí)間之后再次被訪問,才會(huì)將該數(shù)據(jù)轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表的頭部,該參數(shù)默認(rèn)是 1000 單位為:毫秒,也就是1秒之后,如果該數(shù)據(jù)又被訪問了,那么這個(gè)時(shí)候才會(huì)將該數(shù)據(jù)從 LRU 鏈表的冷數(shù)據(jù)區(qū)轉(zhuǎn)移到熱數(shù)據(jù)區(qū)。
現(xiàn)在再回頭看下上面的問題
# 通過預(yù)加載(加載相鄰數(shù)據(jù)頁(yè))進(jìn)來(lái)的數(shù)據(jù)
1. 這個(gè)時(shí)候就很好理解了,反正數(shù)據(jù)會(huì)被放在LRU鏈表的冷數(shù)據(jù)區(qū)的(注意:這里說(shuō)的放在鏈表中的數(shù)據(jù)都是指的是< 緩存頁(yè)中的數(shù)據(jù)所對(duì)應(yīng)的描述數(shù)據(jù)>),當(dāng)在指定時(shí)候之后,如果某些緩存頁(yè)被訪問了那么就將該緩存頁(yè)的描述數(shù)據(jù)放到熱數(shù)據(jù)區(qū)鏈表的頭部
# 全表掃描加載進(jìn)來(lái)的數(shù)據(jù)頁(yè)
1.和上面一樣,數(shù)據(jù)都是先在冷數(shù)據(jù)區(qū),然后在一定時(shí)間之后,再次被訪問到的數(shù)據(jù)頁(yè)才會(huì)轉(zhuǎn)移到熱數(shù)據(jù)區(qū)的鏈表的頭結(jié)點(diǎn),所以這也就很好的解決了全表掃描所帶來(lái)的問題
再來(lái)思考下 Buffer Pool 內(nèi)存不夠的問題
# Buffer Pool 內(nèi)存空間不夠使用了怎么辦?也就是說(shuō)沒有足夠使用的空閑的緩存頁(yè)了。
1.這個(gè)問題在這個(gè)時(shí)候就顯得非常簡(jiǎn)單了,直接將鏈表冷數(shù)據(jù)區(qū)的尾節(jié)點(diǎn)的描述數(shù)據(jù)多對(duì)應(yīng)的緩存頁(yè)刷到磁盤即可。
但是這樣子還不是足夠完美,為什么這么說(shuō),剛剛我們一直在討論的是冷數(shù)據(jù)區(qū)的數(shù)據(jù)被訪問,然后在一定規(guī)則之下會(huì)被加載到熱數(shù)據(jù)鏈表的頭部,但是現(xiàn)在某個(gè)請(qǐng)求需要訪問的數(shù)據(jù)就在熱數(shù)據(jù)區(qū),那是不是直接把該數(shù)據(jù)所在的緩存頁(yè)對(duì)應(yīng)的描述數(shù)據(jù)轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表頭部呢?
很顯然不是這樣子的,因?yàn)闊釘?shù)據(jù)區(qū)的數(shù)據(jù)本身就是會(huì)被頻繁訪問的,這樣子如果每次訪問都去移動(dòng)鏈表,勢(shì)必造成性能的下降(影響再小極端情況下也可能會(huì)不可控),所以 MySQL針對(duì)熱數(shù)據(jù)區(qū)的數(shù)據(jù)的轉(zhuǎn)移也有相關(guān)的規(guī)則。
該規(guī)則就是: 如果被訪問的數(shù)據(jù)所在的緩存頁(yè)在熱數(shù)據(jù)區(qū)的前25%,那么該緩存頁(yè)對(duì)應(yīng)的描述數(shù)據(jù)是不會(huì)被轉(zhuǎn)移到熱數(shù)據(jù)鏈表的頭部的,只有當(dāng)被訪問的緩存頁(yè)對(duì)應(yīng)的描述數(shù)據(jù)在熱數(shù)據(jù)區(qū)鏈表的后75%,該緩存頁(yè)的描述數(shù)據(jù)才會(huì)被轉(zhuǎn)移到熱數(shù)據(jù)鏈表的頭部。
舉個(gè)例子來(lái)說(shuō),假設(shè)熱數(shù)據(jù)區(qū)有100個(gè)緩存頁(yè)(這里的緩存頁(yè)還是指的是緩存頁(yè)對(duì)應(yīng)的描述數(shù)據(jù),再?gòu)?qiáng)調(diào)下,鏈表中存放的是緩存頁(yè)的描述數(shù)據(jù),為了方便有時(shí)候會(huì)直接說(shuō)緩存頁(yè)。希望朋友們注意),當(dāng)被訪問的緩存頁(yè)在前25個(gè)的時(shí)候,熱數(shù)據(jù)區(qū)的鏈表是不會(huì)有變化的,當(dāng)被訪問的緩存頁(yè)在26~100(也就是數(shù)據(jù)在熱數(shù)據(jù)區(qū)鏈表的后75%里面)的時(shí)候,這個(gè)時(shí)候被訪問的緩存頁(yè)才會(huì)被轉(zhuǎn)移到鏈表的頭部。
到此為止, MySQL對(duì)于LUR 鏈表的優(yōu)化就堪稱完美了。是不是看到這里瞬間感覺很多東西都明朗了,好了,對(duì)于 LRU 鏈表我們就討論到這里了。
10、Buffer Pool 中的鏈表小結(jié)
# free鏈表
用來(lái)存放空閑的緩存頁(yè)的描述數(shù)據(jù),如果某個(gè)緩存頁(yè)被使用了,那么該緩存頁(yè)對(duì)應(yīng)的描述數(shù)據(jù)就會(huì)被從 free鏈表中移除
# flush鏈表
被修改的臟數(shù)據(jù)都記錄在 Flush中,同時(shí)會(huì)有一個(gè)后臺(tái)線程會(huì)不定時(shí)的將 Flush中記錄的描述數(shù)據(jù)對(duì)應(yīng)的緩存頁(yè)刷新到磁盤中,如果某個(gè)緩存頁(yè)被刷新到磁盤中了,那么該緩存頁(yè)對(duì)應(yīng)的描述數(shù)據(jù)會(huì)從 Flush中移除,同時(shí)也會(huì)從LRU鏈表中移除(因?yàn)樵摂?shù)據(jù)已經(jīng)不在 Buffer Pool 中了,已經(jīng)被刷入到磁盤,所以就也沒必要記錄在 LRU 鏈表中了),同時(shí)還會(huì)將該緩存頁(yè)的描述數(shù)據(jù)添加到free鏈表中,因?yàn)樵摼彺骓?yè)變得空閑了。
# LRU鏈表
數(shù)據(jù)頁(yè)被加載到 Buffer Pool 中的對(duì)應(yīng)的緩存頁(yè)后,同時(shí)會(huì)將緩存頁(yè)對(duì)應(yīng)的描述數(shù)據(jù)放到 LRU 鏈表的冷數(shù)據(jù)的頭部,當(dāng)在一定時(shí)間過后,冷數(shù)據(jù)區(qū)的數(shù)據(jù)被再次訪問了,就會(huì)將其轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表的頭部,如果被訪問的數(shù)據(jù)就在熱數(shù)據(jù)區(qū),那么如果是在前25%就不會(huì)移動(dòng),如果在后75%仍然會(huì)將其轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表的頭部
后臺(tái)線程將冷數(shù)據(jù)區(qū)的尾節(jié)點(diǎn)的描述數(shù)據(jù)對(duì)應(yīng)的緩存頁(yè)刷入磁盤文件中
11、Buffer Pool 的并發(fā)性能
我們平時(shí)的系統(tǒng)絕對(duì)不可能每次只有一個(gè)請(qǐng)求來(lái)訪問的,說(shuō)白了就是如果多個(gè)請(qǐng)求同時(shí)來(lái)執(zhí)行增刪改,那他們會(huì)并行的去操作 Buffer Pool 中的各種鏈表嗎?如果是并行的會(huì)不會(huì)有什么問題。
實(shí)際上 MySQL在處理這個(gè)問題的時(shí)候考慮的非常簡(jiǎn)單,就是: Buffer Pool 一次只能允許一個(gè)線程來(lái)操作,一次只有一個(gè)線程來(lái)執(zhí)行這一系列的操作,因?yàn)镸ySQL 為了保證數(shù)據(jù)的一致性,操作的時(shí)候必須緩存池加鎖,一次只能有一個(gè)線程獲取到鎖。
這個(gè)時(shí)候,大家這時(shí)候肯定滿腦子問號(hào)。串行那還談什么效率?大家別忘記了,這一系列的操作都是在內(nèi)存中操作的,實(shí)際上這是一個(gè)瞬時(shí)的過程,在內(nèi)存中的操作基本是幾毫秒的甚至微妙級(jí)別的事情。
但是話又說(shuō)回來(lái),串行執(zhí)行再怎么快也是串行,雖然不是性能瓶頸,這還有更好的優(yōu)化辦法嗎?那肯定的 MySQL早就設(shè)計(jì)好了這些規(guī)則。那就是 Buffer Pool 是可以有多個(gè)的,可以通過 MySQL的配置文件來(lái)配置,參數(shù)分別是:
# Buffer Pool 的總大小
innodb_buffer_pool_size= 8589934592
# Buffer Pool 的實(shí)例數(shù)(個(gè)數(shù))
innodb_buffer_pool_instance= 4
一般在生產(chǎn)環(huán)境中,在硬件不緊張的情況下,建議使用此策略。這個(gè)時(shí)候大家是不是又會(huì)有一個(gè)疑問(如果沒有那說(shuō)明你沒認(rèn)真思考哦),大家應(yīng)該有這樣的疑問:
# 問:多個(gè) Buffer Pool 所帶來(lái)的問題思考
在多個(gè)線程訪問不同的 Buffer Pool 那不同的線程加載的數(shù)據(jù)必然是在不同的 Buffer Pool 中,假設(shè) A 線程加載數(shù)據(jù)頁(yè)A到 Buffer Pool A 中,B 線程加載數(shù)據(jù)頁(yè)B到 Buffer Pool B 中,然后兩個(gè)都執(zhí)行完了,這個(gè)時(shí)候 C 線程來(lái)了,他到達(dá)的是 Buffer Pool B中,但是 C 要訪問的數(shù)據(jù)是在 Buffer Pool A中的數(shù)據(jù)頁(yè)上了,這個(gè)時(shí)候 C 還會(huì)去加載數(shù)據(jù)頁(yè)A嗎?,這種情況會(huì)發(fā)生嗎?在不同的 Buffer Pool 緩存中會(huì)去緩存相同的數(shù)據(jù)頁(yè)嗎?
# 答:多個(gè) Buffer Pool 所帶來(lái)的問題解答
這種情況很顯然不會(huì)發(fā)生,既然不會(huì)發(fā)生,那 MySql 是如何解決這種問題的?其實(shí)前面已經(jīng)提到過了,那就是 數(shù)據(jù)頁(yè)緩存哈希表(看下圖),里面存放的是表空間號(hào)+數(shù)據(jù)頁(yè)號(hào) = 緩存頁(yè)地址,所以 MySQL 在加載數(shù)據(jù)所在的數(shù)據(jù)頁(yè)的時(shí)候根據(jù)這一系列的映射關(guān)系判斷數(shù)據(jù)頁(yè)是否被加載,被加載到了那個(gè)緩存頁(yè)中,所以 MySQL 能夠精確的確定某個(gè)數(shù)據(jù)頁(yè)是否被加載,被加載的到了哪個(gè)緩存頁(yè),絕不可能出現(xiàn)重復(fù)加載的情況。
12、動(dòng)態(tài)調(diào)整 Buffer Pool 的大小
到此為止,本文已經(jīng)詳細(xì)的介紹了 Buffer Pool 的內(nèi)存結(jié)構(gòu),它的數(shù)據(jù)是如何存放的,如何刷磁盤的,又是如何加載的,以什么樣的形式存在的等等知識(shí)點(diǎn),下面我們繼續(xù)挖掘,將 Buffer Pool 的相關(guān)知識(shí)點(diǎn)一次說(shuō)個(gè)夠。我們現(xiàn)在來(lái)討論下 Buffer Pool 的大小能否動(dòng)態(tài)調(diào)整。
假設(shè)我們現(xiàn)在的 Buffer Pool 的大小是 2GB大小,現(xiàn)在想將其擴(kuò)大到 4GB,現(xiàn)在說(shuō)一下如果真的要這么做,我們的 MySq 需要做哪些事情。首先 , MySQL需要向操作系統(tǒng)申請(qǐng)一塊大小為 4G 的連續(xù)的地址連續(xù)的內(nèi)存空間,然后將原來(lái)的 Buffer Pool 中的數(shù)據(jù)拷貝到新的 Buffer Pool 中。
這樣可能嗎?如果原來(lái)的是8G,擴(kuò)大到 16G,那這個(gè)將原來(lái)的數(shù)據(jù)復(fù)制到新的 Buffer Pool 中是不是極為耗時(shí)的,所以這樣的操作 MySQL必然是不支持的。但實(shí)際上這樣的需求是客觀存在的,那 MySQL是如何解決的呢?
為了處理這種情況, MySQL設(shè)計(jì)出 chunk (http 協(xié)議中也有使用到這個(gè)思想,所以我們會(huì)發(fā)現(xiàn)很多技術(shù)的優(yōu)秀思想都是在相互借鑒)機(jī)制來(lái)解決的
# 什么是chunk機(jī)制
chunk是 MySQL 設(shè)計(jì)的一種機(jī)制,這種機(jī)制的原理是將 Buffer Pool 拆分一個(gè)一個(gè)大小相等的 chunk 塊,每個(gè) chunk 默認(rèn)大小為 128M(可以通過參數(shù)innodb_buffer_pool_chunk_size 來(lái)調(diào)整大小),也就是說(shuō) Buffer Pool 是由一個(gè)個(gè)的chunk組成的
假設(shè) Buffer Pool 大小是 2GB,而一個(gè)chunk大小默認(rèn)是 128M,也就是說(shuō)一個(gè) 2GB大小的 Buffer Pool 里面由 16個(gè) chunk 組成,每個(gè)chunk中有自己的緩存頁(yè)和描述數(shù)據(jù),而 free鏈表、flush 鏈表和 lru 鏈表是共享的
如果說(shuō)有多個(gè) Buffer Pool ,那就是這樣
說(shuō)到這里好像還是沒有說(shuō)到 MySQL到底是如何通過 chunk 機(jī)制來(lái)調(diào)整大小的。實(shí)際上是這樣的,假設(shè)現(xiàn)在 Buffer Pool 有 2GB,里面有16個(gè)chunk,現(xiàn)在想要擴(kuò)大到 4GB,那么這個(gè)時(shí)候只需要新申請(qǐng)一個(gè)個(gè)的 chunk 就可以了。
這樣不但不需要申請(qǐng)一塊很大的連續(xù)的空間,更不需要將復(fù)制數(shù)據(jù)。這樣就能達(dá)到動(dòng)態(tài)調(diào)整大小了(不會(huì)還有人問:這只是擴(kuò)大,怎么縮小呢?gun)。不得不說(shuō) MySQL真機(jī)智。
13、生產(chǎn)環(huán)境如何設(shè)置 Buffer Pool 大小
Buffer Pool 是不是越大越好,理論上是的。那如果一個(gè)機(jī)器內(nèi)存是16GB那分配給 Buffer Pool 15GB,這樣很顯然是不行的,因?yàn)椴僮飨到y(tǒng)要占內(nèi)存,你的機(jī)器上總會(huì)運(yùn)行其他的進(jìn)行的吧?那肯定也是需要占用內(nèi)存的。根據(jù)很多實(shí)際生產(chǎn)經(jīng)驗(yàn)得出的比較合理的大小是機(jī)器內(nèi)存大小的(50%~60%)。
最后一起來(lái)看看你的 INNODB 的相關(guān)參數(shù),命令是 show engine innodb status
showengineinnodbstatus;
----------------------
BufferPool AND MEMORY
----------------------
-- Buffer Pool 的最終大小
Totalmemory allocated
-- Buffer Pool 一共有多少個(gè)緩存頁(yè)
BufferPool size
-- free鏈表中一共有多少個(gè)緩存也是可以使用的
Freebuffers
-- lru鏈表中一共有多少個(gè)緩存頁(yè)
Databasepages
-- lru鏈表鏈表中的冷數(shù)據(jù)區(qū)一共有多少個(gè)緩存頁(yè)
Olddatabase pages
-- flush鏈表中的緩存頁(yè)的數(shù)量
Modifieddb pages
-- 等待從磁盤上加載進(jìn)來(lái)的緩存頁(yè)的數(shù)量
Pendingreads
-- 即將從lru鏈表中刷入磁盤的數(shù)量,flush鏈表中即將刷入磁盤的緩存頁(yè)的數(shù)量
Pending writes: LRU 0, flushlist0, single page 0
-- lru鏈表的冷數(shù)據(jù)區(qū)的緩存頁(yè)被訪問之后轉(zhuǎn)移到熱數(shù)據(jù)區(qū)的緩存頁(yè)的數(shù)量,以及冷數(shù)據(jù)區(qū)里1s之內(nèi)被訪問但是沒有進(jìn)入到熱數(shù)據(jù)區(qū)的緩存頁(yè)的數(shù)量
Pages made young 260368814, notyoung 0
-- 每秒從冷數(shù)據(jù)轉(zhuǎn)移到熱數(shù)據(jù)區(qū)的緩存頁(yè)的數(shù)量,以及每秒在冷數(shù)據(jù)區(qū)被訪問但是沒有進(jìn)入熱數(shù)據(jù)區(qū)的緩存頁(yè)的數(shù)量
332.69 youngs/s, 0.00 non-youngs/s
-- 已經(jīng)讀取創(chuàng)建和寫入的緩存頁(yè)的數(shù)量,以及每秒讀取、創(chuàng)建和寫入的緩存頁(yè)的數(shù)量
Pages read249280313, created 1075315, written 32924991 359.96 reads/s, 0.02 creates/s, 0.23 writes/s
-- 表示1000次訪問中,有多少次是命中了BufferPool緩存中的緩存頁(yè),以及每1000次訪問有多少數(shù)據(jù)從冷數(shù)據(jù)區(qū)轉(zhuǎn)移到熱數(shù)據(jù)區(qū),以及沒有轉(zhuǎn)移的緩存頁(yè)的數(shù)量
Buffer Pool hit rate 867/ 1000, young-making rate 123/ 1000not0/ 1000
-- lru鏈表中緩存頁(yè)的數(shù)量
LRUlen: 8190
--最近50 s讀取磁盤頁(yè)的總數(shù), cur[0]表示現(xiàn)在正在讀取的磁盤頁(yè)的總數(shù)
I/O sum[ 5198] :cur[ 0],
14、結(jié)束語(yǔ)
本篇文章我們?cè)敿?xì)討論了 Buffer Pool 的內(nèi)存結(jié)構(gòu),從 free 鏈表到 lru 鏈表,從 Buffer Pool 到 chunk,從磁盤中加載一個(gè)數(shù)據(jù)頁(yè)到 Buffer Pool 到最后該數(shù)據(jù)頁(yè)又被刷回到磁盤中的一整個(gè)過程,他的每一步都做了什么。
我們一起討論完本文以后,是不是瞬間有種看透來(lái)了 MySQL的感覺,但是這個(gè)僅僅是前提,學(xué)習(xí)這些的目的是為了更好的理解 MySQL讓我們能夠在工作中更加游刃有余地使用它。因?yàn)橹挥性谥懒说讓釉淼那闆r下,才能熟悉他的工作原理,遇到問題才能對(duì)癥下藥。 返回搜狐,查看更多
責(zé)任編輯:
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的mysql+缓冲池脏块率高_什么是数据库的 “缓存池” ?(万字干货)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab节点导纳阵求逆,关于利用矩阵
- 下一篇: 等待读取完毕 java_java – 等