[MySQL]淺談InnoDB存儲(chǔ)引擎(一)緩沖池與LRU

前言

談存儲(chǔ)引擎前征峦,希望讀者先去了解事務(wù)與鎖的基本概念馆纳,這樣會(huì)對(duì)閱讀InnoDB存儲(chǔ)引擎有更好的幫助敌厘。

特性

  1. 行鎖。如果你的數(shù)據(jù)庫(kù)想應(yīng)用在高并發(fā)的場(chǎng)景下舌狗,那么你用來保證事務(wù)安全的鎖粒度必須盡可能小.對(duì)比表鎖茉稠,InnoDB提供了行鎖設(shè)計(jì)。這使得發(fā)生INSERT把夸、UPDATE、DELETE的操作時(shí)铭污,InnoDB只需要鎖定很小的一塊區(qū)域即可保證事務(wù)安全恋日,當(dāng)然,想要達(dá)到這個(gè)效果嘹狞,想要開發(fā)者了解索引知識(shí)和更好地應(yīng)用索引岂膳。
  2. MVCC,多版本并發(fā)控制。
  3. 外鍵約束磅网,用來保證數(shù)據(jù)的邏輯一致性谈截,高并發(fā)的場(chǎng)景下一把不推薦使用外鍵約束,在業(yè)務(wù)層實(shí)現(xiàn)相關(guān)邏輯。
  4. 一致性非鎖定讀簸喂,即在InnoDB下毙死,SELECT操作是默認(rèn)不加鎖的,除非你顯示聲明SELECT FOR UPDATE等操作.

結(jié)構(gòu)

InnoDB內(nèi)部維護(hù)了一個(gè)緩沖池:

  1. 主要負(fù)責(zé)維護(hù)所有進(jìn)程/線程需要訪問的多個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu).
  2. 緩存磁盤上的數(shù)據(jù)喻鳄,方便快速地讀取扼倘,同時(shí)在對(duì)磁盤文件的數(shù)據(jù)修改之前在內(nèi)存池緩存.
  3. redo log緩沖

同時(shí),InnoDB是多線程的模型,多個(gè)線程處理不同的任務(wù).這些后臺(tái)線程的作用如下:

  1. 負(fù)責(zé)刷新內(nèi)存池中的數(shù)據(jù),讓緩沖池的數(shù)據(jù)與磁盤保持一致.
  2. 由于內(nèi)存池中緩存了日志文件,所以事務(wù)發(fā)生異常的時(shí)候朵栖,后臺(tái)線程會(huì)保證InnoDB能恢復(fù)正常.
image.png

這里簡(jiǎn)單介紹一下每個(gè)線程的作用:

  1. Master Thread
    負(fù)責(zé)將緩沖池的數(shù)據(jù)異步刷新到磁盤啥么,保證數(shù)據(jù)的一致性,包括臟頁的刷新越走、合并插入緩沖(INSERT BUFFER)、UNDO頁的回收。
  2. IO Thread
    InnoDB中大量使用了AIO來處理寫IO請(qǐng)求稠诲,進(jìn)而提升數(shù)據(jù)庫(kù)的性能。這個(gè)線程的任務(wù)就是處理這些請(qǐng)求的回調(diào)候址÷来猓可以通過以下指令來查看當(dāng)前數(shù)據(jù)庫(kù)的IO Thread SHOW ENGINE INNODB STATUS
  3. Purge Thread
    事務(wù)提交后,所產(chǎn)生的undolog可能需要被回收岗仑。根據(jù)InnoDB的版本不同匹耕,老一些的版本這件事情是在Master Thread中進(jìn)行的,在InnoDB 1.1后荠雕,這項(xiàng)任務(wù)交由Purage Thread來完成稳其。
  4. Purge Thread
    這個(gè)線程是InnoDB 1.2后引入。負(fù)責(zé)分擔(dān)Master Thread的臟頁刷新操作炸卑。

內(nèi)存

1. 緩沖池

InnoDB是基于磁盤存儲(chǔ)的既鞠,使用頁來管理其中的記錄。由于CPU盖文、內(nèi)存嘱蛋、磁盤之間的速度有著量級(jí)的差異,所以為了讓查詢可以被更快地返回五续,通常需要引入內(nèi)存來提高數(shù)據(jù)庫(kù)的性能洒敏。

內(nèi)存讀
image.png
更新內(nèi)存中的數(shù)據(jù)

對(duì)于數(shù)據(jù)庫(kù)中頁的修改,會(huì)先修改緩沖池中的頁疙驾,再以一定的頻率刷新到磁盤上.
InnoDB并不保證每次頁發(fā)生改變就立刻刷新回磁盤凶伙,而是內(nèi)置了一種Checkpoint機(jī)制進(jìn)行磁盤的回刷。

從上面的分析中我們知道它碎,緩沖池的大小直接影響數(shù)據(jù)庫(kù)的整體性能函荣。同時(shí)显押,內(nèi)存大小的上限也受到操作系統(tǒng)的影響:32位的操作系統(tǒng),最多設(shè)置為3G傻挂。因此乘碑,如果你希望MySQL性能更好,一定要使用64位的操作系統(tǒng).

查看緩沖池信息
# 查看緩沖池參數(shù),這是以字節(jié)為基礎(chǔ)單位的
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
# 如果你希望看到以GB的形式展現(xiàn)踊谋,可以這樣寫
mysql> SELECT @@innodb_buffer_pool_size/1024/1024/1024;
# 查看InnoDB緩沖池中包含數(shù)據(jù)的頁面數(shù)
mysql> SHOW STATUS LIKE 'innodb_buffer_pool_pages_data';
# 查看InnoDB頁面大胁醭稹(默認(rèn)為16KB)。
mysql> SHOW STATUS LIKE 'innodb_page_size';
# 查看InnoDB緩沖池中的頁數(shù)量殖蚕。
mysql> SHOW STATUS LIKE 'innodb_buffer_pool_pages_total';
# 查看緩沖池實(shí)例數(shù)量  
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';
配置緩沖池

這里涉及到緩沖池轿衔、緩沖池塊(innodb_buffer_pool_chunk_size)、緩沖池實(shí)例的概念.

緩沖池塊

當(dāng)InnoDB緩沖池很大時(shí)睦疫,可以通過從內(nèi)存中檢索來滿足許多數(shù)據(jù)請(qǐng)求害驹。使用散列函數(shù),將存儲(chǔ)在緩沖池中或從緩沖池讀取的每個(gè)頁面隨機(jī)分配給其中一個(gè)緩沖池蛤育。每個(gè)緩沖池管理自己的空閑列表宛官,刷新列表,LRU和連接到緩沖池的所有其他數(shù)據(jù)結(jié)構(gòu)瓦糕,并受其自己的緩沖池互斥量保護(hù)底洗。

你可以直接設(shè)置整個(gè)緩沖池的大小,但是會(huì)受到緩沖池塊的影響咕娄。MySQL官方規(guī)定:

緩沖池的大小 = 緩沖池塊大小 * 緩沖池實(shí)例數(shù)量 * N

這如何理解呢?假設(shè)一個(gè)緩沖池總共分配了8G,然后分配了8個(gè)緩沖池實(shí)例,然后緩沖池塊大小為128M,這是有效的亥揖。因?yàn)?G是8*128M的倍數(shù),此時(shí)每個(gè)緩沖池實(shí)例占用1G。

然而圣勒,如果你設(shè)置緩沖池實(shí)例為9G,那么MySQL會(huì)幫你調(diào)整為10G.

# MySQL默認(rèn)的緩沖池只有128M
# 設(shè)置緩沖池的大小,這里為2G,根據(jù)實(shí)際情況而定.  
# 如果你的服務(wù)器是16G以上的费变,建議加大到4G或者8G,當(dāng)你加大到8G的時(shí)候,建議將緩沖池實(shí)例數(shù)也同時(shí)加大圣贸。
mysql> SET GLOBAL innodb_buffer_pool_size =  2147483648;
my.cnf中配置緩沖池塊大小
[mysqld]
innodb_buffer_pool_chunk_size=134217728

注意挚歧,設(shè)置的緩沖池塊大小不可以超過緩沖池實(shí)例的大小,同時(shí)要考慮是否切合公式(緩沖池的大小 = 緩沖池塊大小 * 緩沖池實(shí)例數(shù)量 * N),否則會(huì)被MySQL強(qiáng)制調(diào)整.

配置完成后吁峻,可以通過SHOW ENGINE INNODB STATUS ;指令查看當(dāng)前MySQL的情況滑负。

image.png

InnoDB內(nèi)存數(shù)據(jù)對(duì)象

image.png
內(nèi)存管理LRU

數(shù)據(jù)庫(kù)的緩沖池是通過LRU(Latest Recent Used,最近最少使用)算法進(jìn)行管理的。最頻繁使用的頁在LRU列表的前端用含,而最少使用的頁在LRU列表的尾端橙困。當(dāng)緩沖池不能存放新讀取的頁的時(shí)候,將首先釋放LRU列表中尾端的頁耕餐。

MySQL特殊的LRU算法

MySQL的LRU算法是區(qū)別于傳統(tǒng)的LRU算法的,通常我們稱為midpoint insertion strategy算法:
它會(huì)在LUR列表中標(biāo)記一個(gè)位置辟狈,這個(gè)位置為整個(gè)LRU長(zhǎng)度的5/8.這是由innodb_old_blocks_pct參數(shù)來控制的,你可以通過以下指令來查看這個(gè)參數(shù).

mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct';

如果你認(rèn)為自己的熱點(diǎn)數(shù)據(jù)不止63%肠缔,那么在你可以將閾值進(jìn)行調(diào)整

mysql> SET GLOBAL innodb_old_blocks_pct=20;
  • 當(dāng)InnoDB要將頁面寫入緩沖池中時(shí)夏跷,它首先將其插入中點(diǎn)。
  • 前面5/8的數(shù)據(jù)明未,視為年輕代槽华,后面3/8的數(shù)據(jù),視為老年代趟妥。
  • 當(dāng)訪問老年代的數(shù)據(jù)時(shí)猫态,會(huì)使頁變得年輕,將其移至年輕代的頭部披摄,而如果長(zhǎng)時(shí)間沒有受到訪問亲雪,那么頁將會(huì)老化,直到內(nèi)存緊張時(shí)從LRU列表老年代中移除疚膊。


    image.png

為什么不直接使用LRU义辕?

目的是確保經(jīng)常訪問的(“ 熱 ”)頁保留在緩沖池中,即使預(yù)讀和 全表掃描會(huì)帶來新的塊寓盗,這些塊以后也可能不會(huì)被訪問

  1. 防止因某些SQL操作將大量的頁被刷出灌砖,從而造成緩沖池污染,影響緩沖池的效率傀蚌。常見于索引或者數(shù)據(jù)的掃描操作基显,它們往往需要訪問表中的許多頁,甚至所有頁善炫。而這些頁并不是需要真正返回的或者僅僅是當(dāng)前查詢需要返回撩幽,那么這不是活躍的熱點(diǎn)數(shù)據(jù)。而真正的熱點(diǎn)數(shù)據(jù)此時(shí)被移除了销部,那么InnoDB需要再次訪問硬盤摸航。
  2. 預(yù)讀失效.MySQL會(huì)將未來要讀取的頁提前加載進(jìn)內(nèi)存,省去后續(xù)的IO舅桩。但是這些加載的數(shù)據(jù)可能并不是應(yīng)用程序所需要的酱虎,所以不能判定為是熱點(diǎn)數(shù)據(jù).MySQL將將預(yù)讀數(shù)據(jù)加載進(jìn)緩沖池后,并不馬上把它放入LRU的首部擂涛,而是從midpoint進(jìn)行插入读串,真正被讀取的頁才放入LRU的首部.

MySQL官方解釋道:
防止緩沖池被預(yù)讀攪動(dòng)的優(yōu)化,可以避免由于表或索引掃描而引起的類似問題。在這些掃描中撒妈,通郴峙快速連續(xù)地訪問數(shù)據(jù)頁面幾次,并且再也不會(huì)被訪問狰右。

ok,MySQL采用了midpoint來區(qū)分年輕代和老年代解決了預(yù)讀失效的問題杰捂,那么由于大量的掃描導(dǎo)致內(nèi)存的熱數(shù)據(jù)被移除這件事,MySQL是如何解決的呢?

MySQL使用了innodb_old_blocks_time來決定數(shù)據(jù)是否數(shù)據(jù)是否加入LRU列表的熱端棋蚌。這個(gè)參數(shù)用于表示頁讀取到mid位置后需要等待多久才會(huì)被加入熱點(diǎn)數(shù)據(jù)區(qū)域嫁佳。
你可以通過以下指令開設(shè)定這個(gè)時(shí)間

SET GLOBAL innodb_old_blocks_time=1000;

LRU列表

LRU列表用來管理已經(jīng)讀取的頁挨队,當(dāng)數(shù)據(jù)庫(kù)啟動(dòng)的時(shí)候,LRU列表是空的蒿往,而Free列表則是存放所有的頁盛垦。每當(dāng)需要從緩沖池中分頁時(shí),首先從Free列表中查找是否有可用的空閑頁瓤漏,如果有則將頁從Free列表放入LRU列表中腾夯,否則LRU列表則根據(jù)LRU算法進(jìn)行內(nèi)存淘汰。

image.png

你可以通過以下指令查看LRU列表和Free列表的使用情況

mysql> SHOW ENGINE INNODB STATUS;

其中蔬充,Database pages表示LRU列表中的頁數(shù)量蝶俱,F(xiàn)ree buffers則表示Free列表中的頁數(shù)量,此外你應(yīng)該還關(guān)注到一個(gè)變量-Buffer pool hit rate,表示緩沖池的命中率娃惯。通常來說跷乐,這個(gè)數(shù)值不應(yīng)該小于95%。如果出現(xiàn)異常趾浅,需要觀察是否由于全表掃描引起的LRU列表被污染的問題愕提。

在InnoDB1.2版本后,還支持通過查詢INNODB_BUFFER_POOL_STATS來獲取緩沖池的運(yùn)行狀態(tài).

mysql> SELECT pool_id,hit_rate,pages_made_young,pages_not_made_young FROM
->  information_schema.INNODB_BUFFER_POOL_STATS;

壓縮頁

InnoDB存儲(chǔ)引擎從1.0.x版本開始支持壓縮頁的功能皿哨,即將原來16KB的頁壓縮為1KB浅侨、2KB、3KB证膨、4KB和8KB.而由于頁的大小發(fā)生了變化如输,LRU列表也發(fā)生了改變。其中央勒,對(duì)于小于16KB的頁不见,InnoDB是通過unzip_LRU列表進(jìn)行管理的。通過SHOW ENGINE INNODB STATUS可以獲悉詳情崔步。LRU列表中的頁包含了unzip_LRU列表的頁稳吮。

unzip_LRU列表獲取內(nèi)存的過程

比如當(dāng)前unzip_LRU列表需要申請(qǐng)4KB的大小。

  1. 檢查當(dāng)前4KB的unzip_LRU列表井濒,檢查是否有可用的空閑頁灶似。
  2. 若有,則直接使用瑞你。否則檢查8KB的列表中是否有可用的空閑頁酪惭。
  3. 如果8KB中有空閑頁,那么將8KB拆分成2個(gè)4KB者甲,一個(gè)使用春感,另一個(gè)存入4KB列表的空閑區(qū)。
  4. 如果沒有虏缸,則繼續(xù)往上查找空閑頁甥厦,進(jìn)行拆分纺铭。

臟頁

在LRU列表中的頁被修改后,那么該頁就視為臟頁(dirty page):
例如現(xiàn)在InnoDB進(jìn)行了內(nèi)存插入操作刀疙,但是該數(shù)據(jù)還沒有持久化到硬盤,那這個(gè)剛插入的頁就是臟頁扫倡。這是由于當(dāng)前內(nèi)存的數(shù)據(jù)與硬盤的數(shù)據(jù)不一致導(dǎo)致的谦秧。

MySQL提供了一種CheckPoint機(jī)制將臟頁刷新到硬盤。而Flush列表中的頁即為臟頁撵溃。需要注意的是疚鲤,臟頁即存在于LRU列表中,也存在于Flush列表中缘挑。

你可以通過SHOW ENGINE INNODB STATUS來查看當(dāng)前Flush列表的情況集歇,其中的Modified db pages參數(shù)表示了當(dāng)前臟頁的數(shù)量.

redo log buffer(重做日志緩沖)

InnoDB存儲(chǔ)引擎首先將重做日志信息先放入到這個(gè)緩沖區(qū)中,然后按一定頻率將其刷新到重做日志文件中语淘。redo log buffer一般不需要設(shè)置得過大诲宇,因?yàn)樗⑿碌念l率是很高的(1S),InnoDB默認(rèn)設(shè)置為8MB,通常情況下惶翻,這是足夠的姑蓝。

觸發(fā)redo log buffer刷新的時(shí)機(jī)
  • Master Thread每一秒會(huì)刷新一次
  • 每個(gè)事務(wù)提交的時(shí)候會(huì)刷新一次
  • 當(dāng)redo log buffer剩余空間不足一半的時(shí)候,刷新一次

額外的內(nèi)存池

InnoDB存儲(chǔ)引擎中吕粗,對(duì)內(nèi)存的管理是通過一種稱為內(nèi)存堆的方式進(jìn)行的纺荧。在對(duì)數(shù)據(jù)結(jié)構(gòu)本身的內(nèi)存進(jìn)行分配時(shí),也需要從額外的內(nèi)存池中進(jìn)行申請(qǐng)颅筋,當(dāng)這個(gè)區(qū)域不夠的時(shí)候宙暇,會(huì)從緩沖池中申請(qǐng)。主要用于記錄:緩沖池中的幀緩沖议泵,緩沖控制對(duì)象(LRU占贫、lock、wait)肢簿。

對(duì)緩沖池進(jìn)行擴(kuò)展的時(shí)候靶剑,建議也同時(shí)擴(kuò)展這個(gè)區(qū)域

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市池充,隨后出現(xiàn)的幾起案子桩引,更是在濱河造成了極大的恐慌,老刑警劉巖收夸,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坑匠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡卧惜,警方通過查閱死者的電腦和手機(jī)厘灼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門夹纫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人设凹,你說我怎么就攤上這事舰讹。” “怎么了闪朱?”我有些...
    開封第一講書人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵月匣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我奋姿,道長(zhǎng)锄开,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任称诗,我火速辦了婚禮萍悴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寓免。我一直安慰自己癣诱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開白布再榄。 她就那樣靜靜地躺著狡刘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪困鸥。 梳的紋絲不亂的頭發(fā)上嗅蔬,一...
    開封第一講書人閱讀 52,549評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音疾就,去河邊找鬼澜术。 笑死,一個(gè)胖子當(dāng)著我的面吹牛猬腰,可吹牛的內(nèi)容都是我干的鸟废。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼姑荷,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼盒延!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鼠冕,我...
    開封第一講書人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤添寺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后懈费,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體计露,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了票罐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叉趣。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖该押,靈堂內(nèi)的尸體忽然破棺而出疗杉,到底是詐尸還是另有隱情,我是刑警寧澤蚕礼,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布乡数,位于F島的核電站,受9級(jí)特大地震影響闻牡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绳矩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一罩润、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧翼馆,春花似錦割以、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至中姜,卻和暖如春消玄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丢胚。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工翩瓜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人携龟。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓兔跌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親峡蟋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坟桅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361