一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

 Fengsq501u81r4 2021-07-10

1、Buffer Pool 概述

Buffer Pool 是什么?從字面上看是緩存池的意思,沒(méi)錯(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)過(guò)一些列的操作最終又被刷入到磁盤的一個(gè)過(guò)程,都經(jīng)歷了哪些事情,這個(gè)圖不明白沒(méi)有關(guān)系,因?yàn)楸疚闹攸c(diǎn)是 Buffer Pool 這個(gè)整體的流程就是讓大家稍微有個(gè)印象。

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

2、Buffer Pool 有多大

Buffer Pool 是 InnoDB 中的一塊內(nèi)存區(qū)域,他一定是有自己的大小的,且大小默認(rèn)是 128M,不過(guò)這個(gè)容量似乎有點(diǎn)小了,大家的自己的生產(chǎn)環(huán)境可以根據(jù)實(shí)際的內(nèi)存大小進(jìn)行調(diào)整,參數(shù)為:innodb_buffer_pool_size=2147483648 單位是字節(jié),

# 查看和調(diào)整innodb_buffer_pool_size1. 查看@@innodb_buffer_pool_size大小,單位字節(jié)SELECT @@innodb_buffer_pool_size/1024/1024/1024; #字節(jié)轉(zhuǎn)為G2. 在線調(diào)整InnoDB緩沖池大小,如果不設(shè)置,默認(rèn)為128Mset global innodb_buffer_pool_size = 4227858432; ##單位字節(jié)

他在 InnoDB 中的整體結(jié)構(gòu)大概是這樣子的

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

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 中。

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

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%左右(約800KB)。

#  緩存頁(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è)的大?。?6KB)和對(duì)應(yīng)`緩存頁(yè)*15%`大小(800B左右)的數(shù)據(jù)描述的大小,將內(nèi)存區(qū)域劃分為一個(gè)個(gè)的緩存頁(yè)和對(duì)應(yīng)的描述數(shù)據(jù)
什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

5、Free鏈表

上面是說(shuō)了每個(gè)數(shù)據(jù)頁(yè)會(huì)被加載到一個(gè)緩存頁(yè)中,但是加載的時(shí)候 MySQL是如何知道那個(gè)緩存頁(yè)有數(shù)據(jù),那個(gè)緩存頁(yè)沒(méi)有數(shù)據(jù)呢?換句話說(shuō), MySQL是怎么區(qū)分哪些緩存頁(yè)是空閑的狀態(tài),是可以用來(lái)存放數(shù)據(jù)頁(yè)的。

為了解決這個(gè)問(wèn)題, 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)描述:

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

當(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è)的過(guò)程。

但 MySQL是怎么知道哪些數(shù)據(jù)頁(yè)已經(jīng)被緩存了,哪些沒(méi)有被緩存呢。實(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ì)通過(guò)哈希表中的key來(lái)確定數(shù)據(jù)頁(yè)是否被緩存了。

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

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è)問(wèn)題,MySQL設(shè)計(jì)出了 Flush 鏈表,他的作用就是記錄被修改過(guò)的臟數(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ù)被修改了,但是還沒(méi)有刷新到磁盤中。

同樣的這些已經(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è)臟數(shù)據(jù)頁(yè)頁(yè)被刷新到磁盤后,其空間就騰出來(lái)了,然后又會(huì)跑到 Free 鏈表中了。

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

7、LRU鏈表

如果系統(tǒng)一直在進(jìn)行數(shù)據(jù)庫(kù)的增刪改操作,數(shù)據(jù)庫(kù)內(nèi)部的基本流程就是:

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

我們還拿 redis 類做類比,以便更好的幫助大家明白其原理。Flush 的作用其實(shí)類似 redis 的 key 設(shè)置的過(guò)期時(shí)間,所以一般情況下,redis 內(nèi)存不會(huì)不夠使用,但是總有特殊的情況,問(wèn)題往往就是在這種極端和邊邊角角的情況下產(chǎn)生的。

如果 redis 的內(nèi)存不夠使用了,是不是自己還有一定的淘汰策略?最基本的準(zhǔn)則就是淘汰掉不經(jīng)常使用到的key。Buffer Pool 也類似,它也會(huì)有內(nèi)存不夠使用的情況,它是通過(guò) LRU 鏈表來(lái)維護(hù)的。LRU 即 Least Recently Uesd(最近最少使用)。

MySql 會(huì)把最近使用最少的緩存頁(yè)數(shù)據(jù)刷入到磁盤去,那 MySql 如何判斷出 LRU 數(shù)據(jù)的呢?為此 MySql 專門設(shè)計(jì)了 LRU 鏈表,還引入了另一個(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 的鏈表大致是這樣子的。

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

現(xiàn)在又來(lái)了一個(gè)請(qǐng)求,假設(shè)查詢到的數(shù)據(jù)是已經(jīng)被緩存在緩存頁(yè)B中,這時(shí)候 MySQL就會(huì)將B緩存頁(yè)對(duì)應(yīng)的描述信息插入到LRU鏈表的頭部,如下圖:

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

然后又來(lái)了一個(gè)請(qǐng)求,數(shù)據(jù)是已經(jīng)被緩存在了緩存頁(yè)C中,然后LRU會(huì)變成這樣子:

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

說(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)刷入到磁盤中,以及 Buffer Pool 騰出內(nèi)存空間。來(lái)個(gè)整體的流程圖給大家看下

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

8、LRU鏈表帶來(lái)的麻煩

這里的麻煩指的是就是 MySQL本身的預(yù)讀機(jī)制帶來(lái)的問(wèn)題

# 預(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ì)反問(wèn):一個(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è)也加載到緩存池中。
什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

上圖能夠看到B的相鄰也被加載到了C描述數(shù)據(jù)的前面,而實(shí)際上C的命中率比B的相鄰頁(yè)高多了,這就是LRU本身帶來(lái)的問(wèn)題。

# 哪些情況會(huì)觸發(fā)預(yù)讀機(jī)制 1. 有一個(gè)參數(shù)是 innodb_read_ahead_threshold, 他的默認(rèn)值是56,意思就是如果順序的訪問(wèn)了一個(gè)區(qū)里的多個(gè)數(shù)據(jù)頁(yè),訪問(wèn)的數(shù)據(jù)頁(yè)的數(shù)量超過(guò)了這個(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ì)被訪問(wèn)的,此時(shí)就會(huì)直接觸發(fā)預(yù)讀機(jī)制,把這個(gè)區(qū)里的其他的數(shù)據(jù)頁(yè)都加載到緩存里去(這種就是:隨機(jī)預(yù)讀)隨機(jī)預(yù)讀是通過(guò):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)的問(wèn)題還是蠻大的,既然這么大,那 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ù)。通過(guò)參數(shù)innodb_old_blocks_pct 參數(shù)控制的,默認(rèn)為37,也就是 37% 。用圖表示大致如下:

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

數(shù)據(jù)在從磁盤被加載到緩存池的時(shí)候,首先是會(huì)被放在冷數(shù)據(jù)區(qū)的頭部,然后在一定時(shí)間之后,如果再次訪問(wèn)了這個(gè)數(shù)據(jù),那么這個(gè)數(shù)據(jù)所在的緩存頁(yè)對(duì)應(yīng)描述數(shù)據(jù)就會(huì)被放轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表的頭部。

那為什么說(shuō)是在一定的時(shí)間之后呢,假設(shè)某條數(shù)據(jù)剛被加載到緩存池中,然后緊接著又被訪問(wèn)了一次,這個(gè)時(shí)候假設(shè)就將其轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表的頭部,但是以后就再也不會(huì)被使用了,這樣子是不是就還是會(huì)存在之前的問(wèn)題呢?

所以 MySQL通過(guò)innodb_old_blocks_time來(lái)設(shè)置數(shù)據(jù)被加載到緩存池后的多少時(shí)間之后再次被訪問(wèn),才會(huì)將該數(shù)據(jù)轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表的頭部,該參數(shù)默認(rèn)是1000單位為:毫秒,也就是1秒之后,如果該數(shù)據(jù)又被訪問(wèn)了,那么這個(gè)時(shí)候才會(huì)將該數(shù)據(jù)從 LRU 鏈表的冷數(shù)據(jù)區(qū)轉(zhuǎn)移到熱數(shù)據(jù)區(qū)。

現(xiàn)在再回頭看下上面的問(wèn)題

# 通過(guò)預(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è)被訪問(wèn)了那么就將該緩存頁(yè)的描述數(shù)據(jù)放到熱數(shù)據(jù)區(qū)鏈表的頭部# 全表掃描加載進(jìn)來(lái)的數(shù)據(jù)頁(yè)1. 和上面一樣,數(shù)據(jù)都是先在冷數(shù)據(jù)區(qū),然后在一定時(shí)間之后,再次被訪問(wèn)到的數(shù)據(jù)頁(yè)才會(huì)轉(zhuǎn)移到熱數(shù)據(jù)區(qū)的鏈表的頭結(jié)點(diǎn),所以這也就很好的解決了全表掃描所帶來(lái)的問(wèn)題

再來(lái)思考下 Buffer Pool 內(nèi)存不夠的問(wèn)題

# Buffer Pool 內(nèi)存空間不夠使用了怎么辦?也就是說(shuō)沒(méi)有足夠使用的空閑的緩存頁(yè)了。1. 這個(gè)問(wèn)題在這個(gè)時(shí)候就顯得非常簡(jiǎn)單了,直接將鏈表冷數(shù)據(jù)區(qū)的尾節(jié)點(diǎn)的描述數(shù)據(jù)多對(duì)應(yīng)的緩存頁(yè)刷到磁盤即可。

但是這樣子還不是足夠完美,為什么這么說(shuō),剛剛我們一直在討論的是冷數(shù)據(jù)區(qū)的數(shù)據(jù)被訪問(wèn),然后在一定規(guī)則之下會(huì)被加載到熱數(shù)據(jù)鏈表的頭部,但是現(xiàn)在某個(gè)請(qǐng)求需要訪問(wèn)的數(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ì)被頻繁訪問(wèn)的,這樣子如果每次訪問(wèn)都去移動(dòng)鏈表,勢(shì)必造成性能的下降(影響再小極端情況下也可能會(huì)不可控),所以 MySQL針對(duì)熱數(shù)據(jù)區(qū)的數(shù)據(jù)的轉(zhuǎn)移也有相關(guān)的規(guī)則。

該規(guī)則就是:如果被訪問(wèn)的數(shù)據(jù)所在的緩存頁(yè)在熱數(shù)據(jù)區(qū)的前25%,那么該緩存頁(yè)對(duì)應(yīng)的描述數(shù)據(jù)是不會(huì)被轉(zhuǎn)移到熱數(shù)據(jù)鏈表的頭部的,只有當(dāng)被訪問(wèn)的緩存頁(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)被訪問(wèn)的緩存頁(yè)在前25個(gè)的時(shí)候,熱數(shù)據(jù)區(qū)的鏈表是不會(huì)有變化的,當(dāng)被訪問(wèn)的緩存頁(yè)在26~100(也就是數(shù)據(jù)在熱數(shù)據(jù)區(qū)鏈表的后75%里面)的時(shí)候,這個(gè)時(shí)候被訪問(wèn)的緩存頁(yè)才會(huì)被轉(zhuǎn)移到鏈表的頭部。

到此為止, MySQL對(duì)于LUR 鏈表的優(yōu)化就堪稱完美了。是不是看到這里瞬間感覺(jué)很多東西都明朗了,好了,對(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)被刷入到磁盤,所以就也沒(méi)必要記錄在 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í)間過(guò)后,冷數(shù)據(jù)區(qū)的數(shù)據(jù)被再次訪問(wèn)了,就會(huì)將其轉(zhuǎn)移到熱數(shù)據(jù)區(qū)鏈表的頭部,如果被訪問(wèn)的數(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è)刷入磁盤文件中

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

11、Buffer Pool 的并發(fā)性能

我們平時(shí)的系統(tǒng)絕對(duì)不可能每次只有一個(gè)請(qǐng)求來(lái)訪問(wèn)的,說(shuō)白了就是如果多個(gè)請(qǐng)求同時(shí)來(lái)執(zhí)行增刪改,那他們會(huì)并行的去操作 Buffer Pool 中的各種鏈表嗎?如果是并行的會(huì)不會(huì)有什么問(wèn)題。

實(shí)際上 MySQL在處理這個(gè)問(wèn)題的時(shí)候考慮得非常簡(jiǎn)單,就是: Buffer Pool 一次只能允許一個(gè)線程來(lái)操作,一次只有一個(gè)線程來(lái)執(zhí)行這一系列的操作,因?yàn)镸ySQL 為了保證數(shù)據(jù)的一致性,操作的時(shí)候必須緩存池加鎖,一次只能有一個(gè)線程獲取到鎖。

這個(gè)時(shí)候,大家這時(shí)候肯定滿腦子問(wèn)號(hào)。串行那還談什么效率?大家別忘記了,這一系列的操作都是在內(nèi)存中操作的,實(shí)際上這是一個(gè)瞬時(shí)的過(guò)程,在內(nèi)存中的操作基本是幾毫秒的甚至微妙級(jí)別的事情。

但是話又說(shuō)回來(lái),串行執(zhí)行再怎么快也是串行,雖然不是性能瓶頸,這還有更好的優(yōu)化辦法嗎?那肯定的 MySQL早就設(shè)計(jì)好了這些規(guī)則。那就是 Buffer Pool 是可以有多個(gè)的,可以通過(guò) 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è)疑問(wèn)(如果沒(méi)有那說(shuō)明你沒(méi)認(rèn)真思考哦),大家應(yīng)該有這樣的疑問(wèn):

# 問(wèn):多個(gè) Buffer Pool 所帶來(lái)的問(wèn)題思考    在多個(gè)線程訪問(wèn)不同的 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 要訪問(wèn)的數(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)的問(wèn)題解答    這種情況很顯然不會(huì)發(fā)生,既然不會(huì)發(fā)生,那 MySql 是如何解決這種問(wèn)題的?其實(shí)前面已經(jīng)提到過(guò)了,那就是 數(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ù)加載的情況。
什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

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(可以通過(guò)參數(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 鏈表是共享的
什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

如果說(shuō)有多個(gè) Buffer Pool ,那就是這樣

什么是數(shù)據(jù)庫(kù)的“緩存池”?(萬(wàn)字長(zhǎng)文,絕對(duì)干貨)

說(shuō)到這里好像還是沒(méi)有說(shuō)到 MySQL到底是如何通過(guò) 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ì)還有人問(wèn):這只是擴(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

show engine innodb status;---------------------- Buffer Pool  AND MEMORY------------------------  Buffer Pool 的最終大小Total memory allocated--  Buffer Pool 一共有多少個(gè)緩存頁(yè) Buffer Pool  size-- free 鏈表中一共有多少個(gè)緩存也是可以使用的Free buffers        -- lru鏈表中一共有多少個(gè)緩存頁(yè)Database pages -- lru鏈表鏈表中的冷數(shù)據(jù)區(qū)一共有多少個(gè)緩存頁(yè)Old database pages  -- flush鏈表中的緩存頁(yè)的數(shù)量Modified db pages     -- 等待從磁盤上加載進(jìn)來(lái)的緩存頁(yè)的數(shù)量Pending reads -- 即將從lru鏈表中刷入磁盤的數(shù)量,flush鏈表中即將刷入磁盤的緩存頁(yè)的數(shù)量Pending writes: LRU 0, flush list 0, single page 0-- lru鏈表的冷數(shù)據(jù)區(qū)的緩存頁(yè)被訪問(wèn)之后轉(zhuǎn)移到熱數(shù)據(jù)區(qū)的緩存頁(yè)的數(shù)量,以及冷數(shù)據(jù)區(qū)里1s之內(nèi)被訪問(wèn)但是沒(méi)有進(jìn)入到熱數(shù)據(jù)區(qū)的緩存頁(yè)的數(shù)量Pages made young 260368814, not young 0-- 每秒從冷數(shù)據(jù)轉(zhuǎn)移到熱數(shù)據(jù)區(qū)的緩存頁(yè)的數(shù)量,以及每秒在冷數(shù)據(jù)區(qū)被訪問(wèn)但是沒(méi)有進(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 read 249280313, created 1075315, written 32924991 359.96 reads/s, 0.02 creates/s, 0.23 writes/s-- 表示1000次訪問(wèn)中,有多少次是命中了BufferPool緩存中的緩存頁(yè),以及每1000次訪問(wèn)有多少數(shù)據(jù)從冷數(shù)據(jù)區(qū)轉(zhuǎn)移到熱數(shù)據(jù)區(qū),以及沒(méi)有轉(zhuǎn)移的緩存頁(yè)的數(shù)量 Buffer Pool  hit rate 867 / 1000, young-making rate 123 / 1000 not 0 / 1000-- lru鏈表中緩存頁(yè)的數(shù)量LRU len: 8190-- 最近50s讀取磁盤頁(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è)過(guò)程,他的每一步都做了什么。

我們一起討論完本文以后,是不是瞬間有種看透了 MySQL的感覺(jué),但是這個(gè)僅僅是前提,學(xué)習(xí)這些的目的是為了更好的理解 MySQL讓我們能夠在工作中更加游刃有余地使用他。因?yàn)橹挥性谥懒说讓釉淼那闆r下,才能熟悉他的工作原理,遇到問(wèn)題才能對(duì)癥下藥。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    精品一区二区三区乱码中文| 91在线播放在线播放观看| 亚洲欧洲成人精品香蕉网| 国产精品午夜视频免费观看 | 久久99亚洲小姐精品综合| 日韩一级毛一欧美一级乱| 亚洲品质一区二区三区| 亚洲男人的天堂就去爱| 厕所偷拍一区二区三区视频| 亚洲熟女精品一区二区成人| 亚洲欧洲成人精品香蕉网| 沐浴偷拍一区二区视频| 亚洲免费黄色高清在线观看| 激情亚洲一区国产精品久久| 日韩欧美一区二区不卡视频| 国产一区二区熟女精品免费| 免费在线观看欧美喷水黄片 | 亚洲中文在线中文字幕91| 初尝人妻少妇中文字幕在线| 亚洲中文字幕熟女丝袜久久| 国产激情一区二区三区不卡| 亚洲av熟女国产一区二区三区站 | 亚洲欧美日韩国产自拍| 国产午夜福利在线免费观看| 字幕日本欧美一区二区| 欧洲一区二区三区蜜桃| 中文字幕亚洲视频一区二区| 午夜福利国产精品不卡| 欧美一区二区三区喷汁尤物| 91在线国内在线中文字幕| 国产精品一区二区丝袜| 欧美三级精品在线观看| 99久久国产精品免费| 国产女优视频一区二区| 香港国产三级久久精品三级| 粗暴蹂躏中文一区二区三区| 久久精品中文扫妇内射| 亚洲高清中文字幕一区二三区| 亚洲欧美国产精品一区二区| 亚洲精品深夜福利视频| 偷拍洗澡一区二区三区|